使用 Xcode 和 SDK 4+ 构建胖静态库(设备 + 模拟器) [英] Build fat static library (device + simulator) using Xcode and SDK 4+

查看:34
本文介绍了使用 Xcode 和 SDK 4+ 构建胖静态库(设备 + 模拟器)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从理论上讲,我们似乎可以构建一个包含模拟器、iPhone 和 iPad 的静态库.

但是,Apple 没有我可以找到的关于此的文档,并且 Xcode 的默认模板未配置为执行此操作.

我正在寻找一种可以在 Xcode 内完成的简单、便携、可重用的技术.

一些历史:

  • 在 2008 年,我们曾经能够制作包含 sim 和设备的单个静态库.Apple 禁用了该功能.
  • 在整个 2009 年,我们制作了成对的静态库 - 一个用于 sim,一个用于设备.Apple 现在也禁用了该功能.

参考文献:

  1. 这是一个好主意,它是一个很好的方法,但它不起作用:http://www.drobnik.com/touch/2010/04/universal-static-libraries/

    • 他的脚本中存在一些错误,这意味着它只能在他的机器上运行 - 他应该使用 BUILT_PRODUCTS_DIR 和/或 BUILD_DIR 而不是猜测"它们)
    • Apple 最新的 Xcode 阻止您执行他所做的操作 - 由于 Xcode 处理目标的方式发生了(已记录的)更改,因此它根本无法工作)
  2. 另一位 SO 提问者询问如何在没有 xcode 的情况下做到这一点,其回答侧重于 arm6 与 arm7 部分 - 但忽略了 i386 部分:如何为 armv6、armv7 和 i386 编译静态库 (fat)

    • 自从 Apple 最新更改以来,模拟器部分不再与 arm6/arm7 的区别相同 - 这是一个不同的问题,见上文)

解决方案

替代方案:

轻松复制/粘贴最新版本(但安装说明可能会发生变化 - 见下文!)

Karl 的库 需要花费更多的精力来设置,但更好的长期解决方案(它将您的库转换为框架).

使用它,然后调整它以添加对存档构建的支持 - cf@Frederik 在下面评论了他用来使这项工作在存档模式下很好地工作的更改.

<小时>

最近的变化:1. 增加了对 iOS 10.x 的支持(同时保持对旧平台的支持)

  1. 关于如何将这个脚本与嵌入在另一个项目中的项目一起使用的信息(尽管我强烈建议永远不要这样做 - 如果您嵌入项目,Apple 在 Xcode 中有几个显示错误从 Xcode 3.x 到 Xcode 4.6.x)

  2. 让您自动包含捆绑包的奖励脚本(即包含库中的 PNG 文件、PLIST 文件等!) - 见下文(滚动到底部)

  3. 现在支持 iPhone5(使用 Apple 解决 lipo 中的错误的方法).注意:安装说明已更改(我可能可以通过将来更改脚本来简化此操作,但现在不想冒险)

  4. 复制标头"部分现在尊重公共标头位置的构建设置(由 Frederik Wallner 提供)

  5. 添加了 SYMROOT 的显式设置(也许还需要设置 OBJROOT?),感谢 Doug Dickinson

<小时>

SCRIPT(这是您必须复制/粘贴的内容)

有关使用/安装说明,请参见下文

######################################### c.f.https://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4## 版本 2.82## 最新变化:# - 进行更多调整以使 iOS 10+ 和 9- 正常工作# - 支持 iOS 10+# - 更正了 iOS 1-10+ 的拼写错误(感谢 @stuikomma)## 目的:# 在 XCode 中自动为 iPhone + iPad + iPhone 模拟器创建通用静态库## 作者:Adam Martin - http://twitter.com/redglassesapps# 基于:来自 Eonil 的原始脚本(主要变化:Eonil 的脚本在 Xcode GUI 中不起作用 - 它会导致您的计算机崩溃)#设置 -eset -o 管道故障#################[ 测试:帮助解决 Xcode 中的任何未来错误 ]#########DEBUG_THIS_SCRIPT="假"if [ $DEBUG_THIS_SCRIPT = "true" ]然后回声########### 测试#############"echo "调试此脚本时使用以下变量;注意它们可能会在递归时更改"echo "BUILD_DIR = $BUILD_DIR"echo "BUILD_ROOT = $BUILD_ROOT"echo "CONFIGURATION_BUILD_DIR = $CONFIGURATION_BUILD_DIR"echo "BUILT_PRODUCTS_DIR = $BUILT_PRODUCTS_DIR"echo "CONFIGURATION_TEMP_DIR = $CONFIGURATION_TEMP_DIR"echo "TARGET_BUILD_DIR = $TARGET_BUILD_DIR"菲#####################[ 第1部分 ]################### 首先,计算出 BASESDK 版本号(注意:Apple 应该报告这一点,但他们隐藏了它)#(顺便说一句:在 sh 中搜索子字符串是一场噩梦!呜咽)SDK_VERSION=$(echo ${SDK_NAME} | grep -o 'd{1,2}.d{1,2}$')# 接下来,确定我们是在 SIM 还是 DEVICEif [ ${PLATFORM_NAME} = "iphonesimulator" ]然后OTHER_SDK_TO_BUILD=iphoneos${SDK_VERSION}别的OTHER_SDK_TO_BUILD=iphonesimulator${SDK_VERSION}菲echo "XCode 选择了 SDK: ${PLATFORM_NAME} 版本: ${SDK_VERSION} (虽然反向定位: ${IPHONEOS_DEPLOYMENT_TARGET})"echo "...因此,OTHER_SDK_TO_BUILD = ${OTHER_SDK_TO_BUILD}"#####################[ 第 1 部分结束 ]#######################################[ 第2部分 ]#################### 如果这是原始调用,则调用需要其他构建的任何内容## Xcode 已经在构建一个目标...## ...但这是一个库,所以苹果将它设置为只构建一个是错误的.# ...我们需要构建所有目标# ...我们不能重新构建已经构建的目标:如果你尝试这个,Xcode 会崩溃你的计算机(无限递归!)### 所以:只构建缺少的平台/配置.if [ "true" == ${ALREADYINVOKED:-false} ]然后echo "递归:我不是根调用,所以我不会递归"别的# 危急:# 防止无限递归(Xcode 很烂)导出 ALREADYINVOKED="true"echo "递归:我是根......现在递归所有丢失的构建目标......"echo "RECURSION: ...即将调用: xcodebuild -configuration "${CONFIGURATION}" -project "${PROJECT_NAME}.xcodeproj" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO" BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}"xcodebuild -configuration "${CONFIGURATION}" -project "${PROJECT_NAME}.xcodeproj" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO BUILD_DIR="${BUILD_DIR"BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}"行动=构建"#将所有平台二进制文件合并为每个配置的胖二进制文件.# 计算(多个)构建文件的来源:CURRENTCONFIG_DEVICE_DIR=${SYMROOT}/${CONFIGURATION}-iphoneosCURRENTCONFIG_SIMULATOR_DIR=${SYMROOT}/${CONFIGURATION}-iphonesimulatorecho "从以下位置获取设备构建:${CURRENTCONFIG_DEVICE_DIR}"echo "从 ${CURRENTCONFIG_SIMULATOR_DIR} 获取模拟器构建"CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-通用echo "...我将输出一个通用版本到:${CREATING_UNIVERSAL_DIR}"# ... 删除此脚本之前运行的产品# 注意:此目录仅由此脚本创建 - 删除应该是安全的!rm -rf "${CREATING_UNIVERSAL_DIR}"mkdir "${CREATING_UNIVERSAL_DIR}"#echo "lipo: for current configuration (${CONFIGURATION}) 创建输出文件:${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}"xcrun -sdk iphoneos lipo -create -output "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}"############ 添加:StackOverflow 建议也复制包含"文件#(未经测试,但应该可以正常工作)#echo "从 ${PUBLIC_HEADERS_FOLDER_PATH} 获取头文件"echo "(如果你将你的库项目嵌入到另一个项目中,你将需要添加"回声一个用户搜索标题"构建设置:(注意包括下面的双引号!)"echo '"$(TARGET_BUILD_DIR)/usr/local/include/"'if [ -d "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" ]然后mkdir -p "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"# * 需要在双引号之外吗?cp -r "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"* "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"菲菲

<小时>

安装说明

  1. 创建一个静态库项目
  2. 选择目标
  3. 在Build Settings"选项卡中,将Build Active Architecture Only"设置为NO"(对于所有项)
  4. 在Build Phases"选项卡中,选择Add ... New Build Phase ... New Run Script Build Phase"
  5. 将脚本(以上)复制/粘贴到框中

...奖金可选用法:

  1. 可选:如果您的库中有标题,请将它们添加到复制标题"阶段
  2. 可选:...并将它们从项目"部分拖放到公共"部分
  3. 可选:......每次构建应用程序时,它们都会自动导出到调试通用"目录的子目录中(它们将在 usr/local/include 中)
  4. 可选:注意:如果您尝试将您的项目拖放到另一个 Xcode 项目中,这会暴露 Xcode 4 中的一个错误,如果您有公共标题,它就无法创建 .IPA 文件在您的拖放项目中.解决方法:不要嵌入 xcode 项目(Apple 代码中的错误太多!)

如果找不到输出文件,这里有一个解决方法:

  1. 将以下代码添加到脚本的最后(由 Frederik Wallner 提供):打开${CREATING_UNIVERSAL_DIR}"

  2. Apple 删除 200 行后的所有输出.选择你的目标,在运行脚本阶段,你必须取消勾选:在构建日志中显示环境变量"

  3. 如果您为 XCode4 使用自定义的构建输出"目录,那么 XCode 会将您所有的意外"文件放在错误的位置.

    1. 构建项目
    2. 点击右侧最后一个图标,位于 Xcode4 左上角区域.
    3. 选择顶部项目(这是您的最新版本".Apple 应该自动选择它,但他们没有想到这一点)
    4. 在主窗口中,滚动到底部.最后一行应该是: lipo: for current configuration (Debug) created output file:/Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetName.a

    ...那是您的通用构建的位置.

<小时>

如何在项目中包含非源代码"文件(PNG、PLIST、XML 等)

  1. 执行以上所有操作,检查是否有效
  2. 在第一个之后创建一个新的运行脚本阶段(复制/粘贴下面的代码)
  3. 在 Xcode 中创建一个新的 Target,类型为bundle"
  4. 在您的主要项目的构建阶段"中,将新包添加为它依赖"的内容(顶部,点击加号按钮,滚动到底部,在您的产品中找到.bundle"文件)
  5. 在您的 NEW BUNDLE TARGET 中的Build Phases"中,添加Copy Bundle Resources"部分,然后将所有 PNG 文件等拖放到其中

用于将构建的包自动复制到与 FAT 静态库相同的文件夹中的脚本:

echo "RunScript2:"echo 将任何包自动复制到 RunScript1 创建的‘通用’输出文件夹中"CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-通用cp -r "${BUILT_PRODUCTS_DIR}/"*.bundle "${CREATING_UNIVERSAL_DIR}"

It appears that we can - theoretically - build a single static library that includes both simulator and iPhone and iPad.

However, Apple has no documentation on this that I can find, and Xcode's default templates are NOT configured to do this.

I'm looking for a simple, portable, re-usable technique that can be done inside Xcode.

Some history:

  • In 2008, we used to be able to make single static-libs that included both sim and device. Apple disabled that.
  • Throughout 2009, we made pairs of static libs - one for sim, one for device. Apple has now disabled that too.

References:

  1. This is a great idea, it's an excellent approach, but it doesn't work: http://www.drobnik.com/touch/2010/04/universal-static-libraries/

    • There's some bugs in his script that means it only works on his machine - he should be using BUILT_PRODUCTS_DIR and/or BUILD_DIR instead of "guesstimating" them)
    • Apple's latest Xcode prevents you from doing what he's done - it simply will not work, due to the (Documented) change in how Xcode processes targets)
  2. Another SO questioner asked how to do it WITHOUT xcode, and with responses that focussed on the arm6 vs arm7 part - but ignored the i386 part: How do i compile a static library (fat) for armv6, armv7 and i386

    • Since Apple's latest changes, the Simulator part isn't the same as the arm6/arm7 difference any more - it's a different problem, see above)

解决方案

ALTERNATIVES:

Easy copy/paste of latest version (but install instructions may change - see below!)

Karl's library takes much more effort to setup, but much nicer long-term solution (it converts your library into a Framework).

Use this, then tweak it to add support for Archive builds - c.f. @Frederik's comment below on the changes he's using to make this work nicely with Archive mode.


RECENT CHANGES: 1. Added support for iOS 10.x (while maintaining support for older platforms)

  1. Info on how to use this script with a project-embedded-in-another-project (although I highly recommend NOT doing that, ever - Apple has a couple of show-stopper bugs in Xcode if you embed projects inside each other, from Xcode 3.x through to Xcode 4.6.x)

  2. Bonus script to let you auto-include Bundles (i.e. include PNG files, PLIST files etc from your library!) - see below (scroll to bottom)

  3. now supports iPhone5 (using Apple's workaround to the bugs in lipo). NOTE: the install instructions have changed (I can probably simplify this by changing the script in future, but don't want to risk it now)

  4. "copy headers" section now respects the build setting for the location of the public headers (courtesy of Frederik Wallner)

  5. Added explicit setting of SYMROOT (maybe need OBJROOT to be set too?), thanks to Doug Dickinson


SCRIPT (this is what you have to copy/paste)

For usage / install instructions, see below

##########################################
#
# c.f. https://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4
#
# Version 2.82
#
# Latest Change:
# - MORE tweaks to get the iOS 10+ and 9- working
# - Support iOS 10+
# - Corrected typo for iOS 1-10+ (thanks @stuikomma)
# 
# Purpose:
#   Automatically create a Universal static library for iPhone + iPad + iPhone Simulator from within XCode
#
# Author: Adam Martin - http://twitter.com/redglassesapps
# Based on: original script from Eonil (main changes: Eonil's script WILL NOT WORK in Xcode GUI - it WILL CRASH YOUR COMPUTER)
#

set -e
set -o pipefail

#################[ Tests: helps workaround any future bugs in Xcode ]########
#
DEBUG_THIS_SCRIPT="false"

if [ $DEBUG_THIS_SCRIPT = "true" ]
then
echo "########### TESTS #############"
echo "Use the following variables when debugging this script; note that they may change on recursions"
echo "BUILD_DIR = $BUILD_DIR"
echo "BUILD_ROOT = $BUILD_ROOT"
echo "CONFIGURATION_BUILD_DIR = $CONFIGURATION_BUILD_DIR"
echo "BUILT_PRODUCTS_DIR = $BUILT_PRODUCTS_DIR"
echo "CONFIGURATION_TEMP_DIR = $CONFIGURATION_TEMP_DIR"
echo "TARGET_BUILD_DIR = $TARGET_BUILD_DIR"
fi

#####################[ part 1 ]##################
# First, work out the BASESDK version number (NB: Apple ought to report this, but they hide it)
#    (incidental: searching for substrings in sh is a nightmare! Sob)

SDK_VERSION=$(echo ${SDK_NAME} | grep -o 'd{1,2}.d{1,2}$')

# Next, work out if we're in SIM or DEVICE

if [ ${PLATFORM_NAME} = "iphonesimulator" ]
then
OTHER_SDK_TO_BUILD=iphoneos${SDK_VERSION}
else
OTHER_SDK_TO_BUILD=iphonesimulator${SDK_VERSION}
fi

echo "XCode has selected SDK: ${PLATFORM_NAME} with version: ${SDK_VERSION} (although back-targetting: ${IPHONEOS_DEPLOYMENT_TARGET})"
echo "...therefore, OTHER_SDK_TO_BUILD = ${OTHER_SDK_TO_BUILD}"
#
#####################[ end of part 1 ]##################

#####################[ part 2 ]##################
#
# IF this is the original invocation, invoke WHATEVER other builds are required
#
# Xcode is already building ONE target...
#
# ...but this is a LIBRARY, so Apple is wrong to set it to build just one.
# ...we need to build ALL targets
# ...we MUST NOT re-build the target that is ALREADY being built: Xcode WILL CRASH YOUR COMPUTER if you try this (infinite recursion!)
#
#
# So: build ONLY the missing platforms/configurations.

if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: I am NOT the root invocation, so I'm NOT going to recurse"
else
# CRITICAL:
# Prevent infinite recursion (Xcode sucks)
export ALREADYINVOKED="true"

echo "RECURSION: I am the root ... recursing all missing build targets NOW..."
echo "RECURSION: ...about to invoke: xcodebuild -configuration "${CONFIGURATION}" -project "${PROJECT_NAME}.xcodeproj" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO" BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}"

xcodebuild -configuration "${CONFIGURATION}" -project "${PROJECT_NAME}.xcodeproj" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}"

ACTION="build"

#Merge all platform binaries as a fat binary for each configurations.

# Calculate where the (multiple) built files are coming from:
CURRENTCONFIG_DEVICE_DIR=${SYMROOT}/${CONFIGURATION}-iphoneos
CURRENTCONFIG_SIMULATOR_DIR=${SYMROOT}/${CONFIGURATION}-iphonesimulator

echo "Taking device build from: ${CURRENTCONFIG_DEVICE_DIR}"
echo "Taking simulator build from: ${CURRENTCONFIG_SIMULATOR_DIR}"

CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal
echo "...I will output a universal build to: ${CREATING_UNIVERSAL_DIR}"

# ... remove the products of previous runs of this script
#      NB: this directory is ONLY created by this script - it should be safe to delete!

rm -rf "${CREATING_UNIVERSAL_DIR}"
mkdir "${CREATING_UNIVERSAL_DIR}"

#
echo "lipo: for current configuration (${CONFIGURATION}) creating output file: ${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}"
xcrun -sdk iphoneos lipo -create -output "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}"

#########
#
# Added: StackOverflow suggestion to also copy "include" files
#    (untested, but should work OK)
#
echo "Fetching headers from ${PUBLIC_HEADERS_FOLDER_PATH}"
echo "  (if you embed your library project in another project, you will need to add"
echo "   a "User Search Headers" build setting of: (NB INCLUDE THE DOUBLE QUOTES BELOW!)"
echo '        "$(TARGET_BUILD_DIR)/usr/local/include/"'
if [ -d "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" ]
then
mkdir -p "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"
# * needs to be outside the double quotes?
cp -r "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"* "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"
fi
fi


INSTALL INSTRUCTIONS

  1. Create a static lib project
  2. Select the Target
  3. In "Build Settings" tab, set "Build Active Architecture Only" to "NO" (for all items)
  4. In "Build Phases" tab, select "Add ... New Build Phase ... New Run Script Build Phase"
  5. Copy/paste the script (above) into the box

...BONUS OPTIONAL usage:

  1. OPTIONAL: if you have headers in your library, add them to the "Copy Headers" phase
  2. OPTIONAL: ...and drag/drop them from the "Project" section to the "Public" section
  3. OPTIONAL: ...and they will AUTOMATICALLY be exported every time you build the app, into a sub-directory of the "debug-universal" directory (they will be in usr/local/include)
  4. OPTIONAL: NOTE: if you also try to drag/drop your project into another Xcode project, this exposes a bug in Xcode 4, where it cannot create an .IPA file if you have Public Headers in your drag/dropped project. The workaround: dont' embed xcode projects (too many bugs in Apple's code!)

If you can't find the output file, here's a workaround:

  1. Add the following code to the very end of the script (courtesy of Frederik Wallner): open "${CREATING_UNIVERSAL_DIR}"

  2. Apple deletes all output after 200 lines. Select your Target, and in the Run Script Phase, you MUST untick: "Show environment variables in build log"

  3. if you're using a custom "build output" directory for XCode4, then XCode puts all your "unexpected" files in the wrong place.

    1. Build the project
    2. Click on the last icon on the right, in the top left area of Xcode4.
    3. Select the top item (this is your "most recent build". Apple should auto-select it, but they didn't think of that)
    4. in the main window, scroll to bottom. The very last line should read: lipo: for current configuration (Debug) creating output file: /Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetName.a

    ...that is the location of your Universal Build.


How to include "non sourcecode" files in your project (PNG, PLIST, XML, etc)

  1. Do everything above, check it works
  2. Create a new Run Script phase that comes AFTER THE FIRST ONE (copy/paste the code below)
  3. Create a new Target in Xcode, of type "bundle"
  4. In your MAIN PROJECT, in "Build Phases", add the new bundle as something it "depends on" (top section, hit the plus button, scroll to bottom, find the ".bundle" file in your Products)
  5. In your NEW BUNDLE TARGET, in "Build Phases", add a "Copy Bundle Resources" section, and drag/drop all the PNG files etc into it

Script to auto-copy the built bundle(s) into same folder as your FAT static library:

echo "RunScript2:"
echo "Autocopying any bundles into the 'universal' output folder created by RunScript1"
CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal
cp -r "${BUILT_PRODUCTS_DIR}/"*.bundle "${CREATING_UNIVERSAL_DIR}"

这篇关于使用 Xcode 和 SDK 4+ 构建胖静态库(设备 + 模拟器)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆