Add IBC support to managed build (#10180)
authorDaniel Podder <dapodd@microsoft.com>
Thu, 16 Mar 2017 21:37:07 +0000 (14:37 -0700)
committerGitHub <noreply@github.com>
Thu, 16 Mar 2017 21:37:07 +0000 (14:37 -0700)
Add a new build switch, `ibcinstrument`, that adds `/Tuning` on the `crossgen`
command line when building System.Private.CoreLib and its peers. Automatically
consume IBC optdata during builds when these conditions are met:

1. `ibcinstrument` is *not* passed to the build,
2. optdata is available
3. ibcmerge is available

Note that `optdata` will not yet be restored with this change; once packages for
master are made available, a new package reference will still need to be added.
This PR attempts to unblock manually testing IBC, as well as surrounding
CI/infrastructure work.

To help produce an IBC-optimized build using manually generated profile data,
run the newly added `tests/scripts/optdata/bootstrap.py` script. It will
configure the build to consume IBC data from a path automatically, and print out
that path where profile data can be copied.

build.cmd
build.proj
build.sh
config.json
src/mscorlib/System.Private.CoreLib.csproj
tests/scripts/optdata/bootstrap.py [new file with mode: 0755]
tests/scripts/optdata/project.json [new file with mode: 0644]

index f0fbb71..6923863 100644 (file)
--- a/build.cmd
+++ b/build.cmd
@@ -62,7 +62,8 @@ set "__SourceDir=%__ProjectDir%\src"
 set "__PackagesDir=%__ProjectDir%\packages"
 set "__RootBinDir=%__ProjectDir%\bin"
 set "__LogsDir=%__RootBinDir%\Logs"
-set "__OptDataVersion="
+set "__PgoOptDataVersion="
+set "__IbcOptDataVersion="
 
 set __BuildAll=
 
@@ -78,6 +79,7 @@ set __BuildJit32="-DBUILD_JIT32=0"
 set __BuildStandaloneGC="-DFEATURE_STANDALONE_GC=0"
 
 set __PgoInstrument=0
+set __IbcTuning=
 
 REM __PassThroughArgs is a set of things that will be passed through to nested calls to build.cmd
 REM when using "all".
@@ -133,6 +135,7 @@ if /i "%1" == "skiprestoreoptdata"  (set __RestoreOptData=0&set processedArgs=!p
 if /i "%1" == "usenmakemakefiles"   (set __NMakeMakefiles=1&set __ConfigureOnly=1&set __BuildNative=1&set __BuildNativeCoreLib=0&set __BuildCoreLib=0&set __BuildTests=0&set __BuildPackages=0&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
 if /i "%1" == "buildjit32"          (set __BuildJit32="-DBUILD_JIT32=1"&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
 if /i "%1" == "pgoinstrument"       (set __PgoInstrument=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
+if /i "%1" == "ibcinstrument"       (set __IbcTuning=/Tuning&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
 if /i "%1" == "toolset_dir"         (set __ToolsetDir=%2&set __PassThroughArgs=%__PassThroughArgs% %2&set processedArgs=!processedArgs! %1 %2&shift&shift&goto Arg_Loop)
 if /i "%1" == "compatjitcrossgen"   (set __CompatJitCrossgen=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
 if /i "%1" == "buildstandalonegc"   (set __BuildStandaloneGC="-DFEATURE_STANDALONE_GC=1"&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop)
@@ -240,7 +243,10 @@ where /q python || (
 set OptDataProjectJsonPath=%__ProjectDir%\src\.nuget\optdata\project.json
 if EXIST "%OptDataProjectJsonPath%" (
     for /f "tokens=*" %%s in ('python "%__ProjectDir%\extract-from-json.py" -rf "%OptDataProjectJsonPath%" dependencies optimization.PGO.CoreCLR') do @(
-        set __OptDataVersion=%%s
+        set __PgoOptDataVersion=%%s
+    )
+    for /f "tokens=*" %%s in ('python "%__ProjectDir%\extract-from-json.py" -rf "%OptDataProjectJsonPath%" dependencies optimization.IBC.CoreCLR') do @(
+        set __IbcOptDataVersion=%%s
     )
 )
 
@@ -302,7 +308,7 @@ if %__BuildNative% EQU 1 (
     echo %__MsgPrefix%Regenerating the Visual Studio solution
 
     pushd "%__IntermediatesDir%"
-    set __ExtraCmakeArgs=!___SDKVersion! "-DCLR_CMAKE_TARGET_OS=%__BuildOs%" "-DCLR_CMAKE_PACKAGES_DIR=%__PackagesDir%" "-DCLR_CMAKE_PGO_INSTRUMENT=%__PgoInstrument%" "-DCLR_CMAKE_OPTDATA_VERSION=%__OptDataVersion%"
+    set __ExtraCmakeArgs=!___SDKVersion! "-DCLR_CMAKE_TARGET_OS=%__BuildOs%" "-DCLR_CMAKE_PACKAGES_DIR=%__PackagesDir%" "-DCLR_CMAKE_PGO_INSTRUMENT=%__PgoInstrument%" "-DCLR_CMAKE_OPTDATA_VERSION=%__PgoOptDataVersion%"
     call "%__SourceDir%\pal\tools\gen-buildsys-win.bat" "%__ProjectDir%" %__VSVersion% %__BuildArch% %__BuildJit32% %__BuildStandaloneGC% !__ExtraCmakeArgs!
        @if defined _echo @echo on
     popd
@@ -356,7 +362,7 @@ if /i "%__DoCrossArchBuild%"=="1" (
     pushd "%__CrossCompIntermediatesDir%"
     set __CMakeBinDir=%__CrossComponentBinDir%
     set "__CMakeBinDir=!__CMakeBinDir:\=/!"
-    set __ExtraCmakeArgs="-DCLR_CROSS_COMPONENTS_BUILD=1" "-DCLR_CMAKE_TARGET_ARCH=%__BuildArch%" "-DCLR_CMAKE_TARGET_OS=%__BuildOs%" "-DCLR_CMAKE_PACKAGES_DIR=%__PackagesDir%" "-DCLR_CMAKE_PGO_INSTRUMENT=%__PgoInstrument%" "-DCLR_CMAKE_OPTDATA_VERSION=%__OptDataVersion%"
+    set __ExtraCmakeArgs="-DCLR_CROSS_COMPONENTS_BUILD=1" "-DCLR_CMAKE_TARGET_ARCH=%__BuildArch%" "-DCLR_CMAKE_TARGET_OS=%__BuildOs%" "-DCLR_CMAKE_PACKAGES_DIR=%__PackagesDir%" "-DCLR_CMAKE_PGO_INSTRUMENT=%__PgoInstrument%" "-DCLR_CMAKE_OPTDATA_VERSION=%__PgoOptDataVersion%"
     call "%__SourceDir%\pal\tools\gen-buildsys-win.bat" "%__ProjectDir%" %__VSVersion% %__CrossArch% !__ExtraCmakeArgs!
     @if defined _echo @echo on
     popd
@@ -401,6 +407,12 @@ if %__BuildCoreLib% EQU 1 (
     set __MsbuildWrn=/flp1:WarningsOnly;LogFile="%__LogsDir%\System.Private.CoreLib_%__BuildOS%__%__BuildArch%__%__BuildType%.wrn"
     set __MsbuildErr=/flp2:ErrorsOnly;LogFile="%__LogsDir%\System.Private.CoreLib_%__BuildOS%__%__BuildArch%__%__BuildType%.err"
 
+    set __ExtraBuildArgs=
+    if not defined __IbcTuning (
+      set __ExtraBuildArgs=!__ExtraBuildArgs! -OptimizationDataDir="%__PackagesDir%/optimization.%__BuildOS%-%__BuildArch%.IBC.CoreCLR/%__IbcOptDataVersion%/data/"
+      set __ExtraBuildArgs=!__ExtraBuildArgs! -EnableProfileGuidedOptimization=true
+    )
+
     if /i "%__BuildArch%" == "arm64" (
                set __nugetBuildArgs=-buildNugetPackage=false
     ) else if "%__SkipNugetPackage%" == "1" (
@@ -409,7 +421,7 @@ if %__BuildCoreLib% EQU 1 (
                set __nugetBuildArgs=-buildNugetPackage=true
        )
 
-    @call %__ProjectDir%\run.cmd build -Project=%__ProjectDir%\build.proj -MsBuildLog=!__MsbuildLog! -MsBuildWrn=!__MsbuildWrn! -MsBuildErr=!__MsbuildErr! !__nugetBuildArgs! %__RunArgs% %__UnprocessedBuildArgs% 
+    @call %__ProjectDir%\run.cmd build -Project=%__ProjectDir%\build.proj -MsBuildLog=!__MsbuildLog! -MsBuildWrn=!__MsbuildWrn! -MsBuildErr=!__MsbuildErr! !__nugetBuildArgs! %__RunArgs% !__ExtraBuildArgs! %__UnprocessedBuildArgs%
     if not !errorlevel! == 0 (
         echo %__MsgPrefix%Error: System.Private.CoreLib build failed. Refer to the build log files for details:
         echo     "%__LogsDir%\System.Private.CoreLib_%__BuildOS%__%__BuildArch%__%__BuildType%.log"
@@ -429,8 +441,8 @@ if %__BuildNativeCoreLib% EQU 1 (
         set COMPlus_UseWindowsX86CoreLegacyJit=1
     )
 
-    echo "%__CrossgenExe%" /Platform_Assemblies_Paths "%__BinDir%" /out "%__BinDir%\System.Private.CoreLib.ni.dll" "%__BinDir%\System.Private.CoreLib.dll"
-    "%__CrossgenExe%" /Platform_Assemblies_Paths "%__BinDir%" /out "%__BinDir%\System.Private.CoreLib.ni.dll" "%__BinDir%\System.Private.CoreLib.dll" > "%__CrossGenCoreLibLog%" 2>&1
+    echo "%__CrossgenExe%" %__IbcTuning% /Platform_Assemblies_Paths "%__BinDir%" /out "%__BinDir%\System.Private.CoreLib.ni.dll" "%__BinDir%\System.Private.CoreLib.dll"
+    "%__CrossgenExe%" %__IbcTuning% /Platform_Assemblies_Paths "%__BinDir%" /out "%__BinDir%\System.Private.CoreLib.ni.dll" "%__BinDir%\System.Private.CoreLib.dll" > "%__CrossGenCoreLibLog%" 2>&1
     if NOT !errorlevel! == 0 (
         echo %__MsgPrefix%Error: CrossGen System.Private.CoreLib build failed. Refer to %__CrossGenCoreLibLog%
         :: Put it in the same log, helpful for Jenkins
@@ -611,6 +623,7 @@ echo     respectively^).
 echo     add nativemscorlib to go further and build the native image for designated mscorlib.
 echo toolset_dir ^<dir^> : set the toolset directory -- Arm64 use only. Required for Arm64 builds.
 echo pgoinstrument: generate instrumented code for profile guided optimization enabled binaries.
+echo ibcinstrument: generate IBC-tuning-enabled native images when invoking crossgen.
 echo configureonly: skip all builds; only run CMake ^(default: CMake and builds are run^)
 echo skipconfigure: skip CMake ^(default: CMake is run^)
 echo skipmscorlib: skip building System.Private.CoreLib ^(default: System.Private.CoreLib is built^).
index 9992bdc..07bb11a 100644 (file)
     <Exec Condition="Exists('$(OptDataProjectJson)')" Command="$(DnuRestoreCommand) &quot;$(OptDataProjectJson)&quot; --source &quot;$(OptDataPackageFeed)&quot;" />
   </Target>
 
+  <!--
+    BuildTools will conditionally restore additional packages, including IBC tools, using the "RestoreOptionalToolingPackages"
+    target, which runs automatically before "Sync". Since no "Sync" target actually exists, go ahead and define one now so that
+    the tools are fetched before "Build".
+  -->
+  <Target Name="Sync" BeforeTargets="Build" />
+
   <Target Name="RestoreNETCorePlatforms" AfterTargets="Build" Condition="'$(RestoreDuringBuild)'=='true'">
     <Exec Command="$(DnuRestoreCommand) &quot;$(SourceDir).nuget/init/project.json&quot; --source https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
   </Target>
index 1e332cf..d914762 100755 (executable)
--- a/build.sh
+++ b/build.sh
@@ -30,6 +30,7 @@ usage()
     echo "crosscomponent - optional argument to build cross-architecture component,"
     echo "               - will use CAC_ROOTFS_DIR environment variable if set."
     echo "pgoinstrument - generate instrumented code for profile guided optimization enabled binaries."
+    echo "ibcinstrument - generate IBC-tuning-enabled native images when invoking crossgen."
     echo "configureonly - do not perform any builds; just configure the build."
     echo "skipconfigure - skip build configuration."
     echo "skipnative - do not build native components."
@@ -304,7 +305,7 @@ build_cross_arch_component()
         fi
     fi
 
-    __ExtraCmakeArgs="-DCLR_CMAKE_TARGET_ARCH=$__BuildArch -DCLR_CMAKE_TARGET_OS=$__BuildOS -DCLR_CMAKE_PACKAGES_DIR=$__PackagesDir -DCLR_CMAKE_PGO_INSTRUMENT=$__PgoInstrument -DCLR_CMAKE_OPTDATA_VERSION=$__OptDataVersion"
+    __ExtraCmakeArgs="-DCLR_CMAKE_TARGET_ARCH=$__BuildArch -DCLR_CMAKE_TARGET_OS=$__BuildOS -DCLR_CMAKE_PACKAGES_DIR=$__PackagesDir -DCLR_CMAKE_PGO_INSTRUMENT=$__PgoInstrument -DCLR_CMAKE_OPTDATA_VERSION=$__PgoOptDataVersion"
     build_native $__SkipCrossArchBuild "$__CrossArch" "$__CrossCompIntermediatesDir" "$__ExtraCmakeArgs" "cross-architecture component"
    
     # restore ROOTFS_DIR, CROSSCOMPONENT, and CROSSCOMPILE 
@@ -366,7 +367,7 @@ build_CoreLib_ni()
 {
     if [ $__SkipCoreCLR == 0 -a -e $__BinDir/crossgen ]; then
         echo "Generating native image for System.Private.CoreLib."
-        $__BinDir/crossgen $__BinDir/System.Private.CoreLib.dll
+        $__BinDir/crossgen $__IbcTuning $__BinDir/System.Private.CoreLib.dll
         if [ $? -ne 0 ]; then
             echo "Failed to generate native image for System.Private.CoreLib."
             exit 1
@@ -399,7 +400,12 @@ build_CoreLib()
     echo "Commencing build of managed components for $__BuildOS.$__BuildArch.$__BuildType"
 
     # Invoke MSBuild
-    $__ProjectRoot/run.sh build -Project=$__ProjectDir/build.proj -MsBuildLog="/flp:Verbosity=normal;LogFile=$__LogsDir/System.Private.CoreLib_$__BuildOS__$__BuildArch__$__BuildType.log" -BuildTarget -__IntermediatesDir=$__IntermediatesDir -__RootBinDir=$__RootBinDir -BuildNugetPackage=false -UseSharedCompilation=false $__RunArgs $__UnprocessedBuildArgs
+    __ExtraBuildArgs=""
+    if [ "$__IbcTuning" -eq "" ]; then
+        __ExtraBuildArgs="$__ExtraBuildArgs -OptimizationDataDir=\"$__PackagesDir/optimization.$__BuildOS-$__BuildArch.IBC.CoreCLR/$__IbcOptDataVersion/data/\""
+        __ExtraBuildArgs="$__ExtraBuildArgs -EnableProfileGuidedOptimization=true"
+    fi
+    $__ProjectRoot/run.sh build -Project=$__ProjectDir/build.proj -MsBuildLog="/flp:Verbosity=normal;LogFile=$__LogsDir/System.Private.CoreLib_$__BuildOS__$__BuildArch__$__BuildType.log" -BuildTarget -__IntermediatesDir=$__IntermediatesDir -__RootBinDir=$__RootBinDir -BuildNugetPackage=false -UseSharedCompilation=false $__RunArgs $__ExtraBuildArgs $__UnprocessedBuildArgs
 
     if [ $? -ne 0 ]; then
         echo "Failed to build managed components."
@@ -550,6 +556,7 @@ __MSBCleanBuildArgs=
 __UseNinja=0
 __VerboseBuild=0
 __PgoInstrument=0
+__IbcTuning=""
 __ConfigureOnly=0
 __SkipConfigure=0
 __SkipRestore=""
@@ -568,7 +575,8 @@ __SkipGenerateVersion=0
 __DoCrossArchBuild=0
 __PortableLinux=0
 __msbuildonunsupportedplatform=0
-__OptDataVersion=""
+__PgoOptDataVersion=""
+__IbcOptDataVersion=""
 
 while :; do
     if [ $# -le 0 ]; then
@@ -672,6 +680,10 @@ while :; do
             __PgoInstrument=1
             ;;
 
+        ibcinstrument)
+            __IbcTuning="/Tuning"
+            ;;
+
         configureonly)
             __ConfigureOnly=1
             __SkipMSCorLib=1
@@ -832,7 +844,8 @@ fi
 # Parse the optdata package version from its project.json file
 optDataProjectJsonPath="$__ProjectRoot/src/.nuget/optdata/project.json"
 if [ -f $optDataProjectJsonPath ]; then
-    __OptDataVersion=$("$__ProjectRoot/extract-from-json.py" -rf $optDataProjectJsonPath dependencies optimization.PGO.CoreCLR)
+    __PgoOptDataVersion=$("$__ProjectRoot/extract-from-json.py" -rf $optDataProjectJsonPath dependencies optimization.PGO.CoreCLR)
+    __IbcOptDataVersion=$("$__ProjectRoot/extract-from-json.py" -rf $optDataProjectJsonPath dependencies optimization.IBC.CoreCLR)
 fi
 
 # init the target distro name
@@ -851,7 +864,7 @@ restore_optdata
 generate_event_logging_sources
 
 # Build the coreclr (native) components.
-__ExtraCmakeArgs="-DCLR_CMAKE_TARGET_OS=$__BuildOS -DCLR_CMAKE_PACKAGES_DIR=$__PackagesDir -DCLR_CMAKE_PGO_INSTRUMENT=$__PgoInstrument -DCLR_CMAKE_OPTDATA_VERSION=$__OptDataVersion"
+__ExtraCmakeArgs="-DCLR_CMAKE_TARGET_OS=$__BuildOS -DCLR_CMAKE_PACKAGES_DIR=$__PackagesDir -DCLR_CMAKE_PGO_INSTRUMENT=$__PgoInstrument -DCLR_CMAKE_OPTDATA_VERSION=$__PgoOptDataVersion"
 build_native $__SkipCoreCLR "$__BuildArch" "$__IntermediatesDir" "$__ExtraCmakeArgs" "CoreCLR component"
 
 # Build cross-architecture components
index 29924b3..03a7054 100644 (file)
       "values": [ "debug", "release", "checked" ],
       "defaultValue": "debug"
     },
+    "EnableProfileGuidedOptimization": {
+      "description": "Enables IBC profile optimizations if profile data are available.",
+      "valueType": "property",
+      "values": [ true, false ],
+      "defaultValue": false
+    },
+    "OptimizationDataDir": {
+      "description": "Sets the path where the build should look for IBC profile data.",
+      "valueType": "property",
+      "values": [],
+      "defaultValue": ""
+    },
     "UseEnv": {
       "description": "Set when building for arm64.",
       "valueType": "property",
       "values": [],
       "defaultValue": ""
     },
+    "OptionalToolSource": {
+      "description": "URL of the nuget feed used by 'optional-tools'",
+      "valueType": "property",
+      "values": [],
+      "defaultValue": ""
+    },
+    "OptionalToolSourceUser": {
+      "description": "User name for authenticating to the optional tools feed",
+      "valueType": "property",
+      "values": [],
+      "defaultValue": ""
+    },
+    "OptionalToolSourcePassword": {
+      "description": "VSTS token for authenticating to the optional tools feed; requires 'package(READ)' permission",
+      "valueType": "property",
+      "values": [],
+      "defaultValue": ""
+    },
     "ExtraParameters": {
       "description": "Extra parameters will be passed to the selected command.",
       "valueType": "passThrough",
       "valueTypes": {}
     }
   }
-}
\ No newline at end of file
+}
index e86f9a3..a0fa860 100644 (file)
     <UseWin32Apis>true</UseWin32Apis>
     <OSGroup>Windows_NT</OSGroup>
   </PropertyGroup>
-  <Import Project="$(ToolsDir)\codeAnalysis.targets" />
-  <Import Project="$(ToolsDir)\Microsoft.CSharp.Targets" />
   <PropertyGroup>
     <StrongNameSig>Silverlight</StrongNameSig>
   </PropertyGroup>
-  <!-- Import signing tools -->
-  <Import Condition="Exists('$(ToolsDir)\sign.targets')" Project="$(ToolsDir)\sign.targets" />
-  <!-- Overwrite the key that we are going to use for signing -->
-  <PropertyGroup>
-    <AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)Tools\Signing\mscorlib.snk</AssemblyOriginatorKeyFile>
-  </PropertyGroup>
   <Import Project="$(MSBuildThisFileDirectory)Tools\Versioning\GenerateVersionInfo.targets" />
-  <!-- Override versioning targets -->
-  <Import Condition="Exists('$(ToolsDir)versioning.targets')" Project="$(ToolsDir)versioning.targets" />
+  <!--
+    Import common targets: codeAnalysis, Microsoft.CSharp, sign, versioning, codeOptimizations, etc.
+    In doing so, override versioning targets.
+  -->
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
   <PropertyGroup>
+    <!-- Overwrite the key that we are going to use for signing -->
+    <AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)Tools\Signing\mscorlib.snk</AssemblyOriginatorKeyFile>
     <!-- Use a different nativeresource file to avoid conflicts with mscorlib-->
     <Win32Resource Condition="'$(GenerateNativeVersionInfo)'=='true'">$(IntermediateOutputPath)\System.Private.CoreLib.res</Win32Resource>
   </PropertyGroup>
diff --git a/tests/scripts/optdata/bootstrap.py b/tests/scripts/optdata/bootstrap.py
new file mode 100755 (executable)
index 0000000..1cf55fa
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+
+"""
+    This script prepares the local source tree to be built with
+    custom optdata. Simply run this script and follow the
+    instructions to inject manually created optdata into the build.
+"""
+
+import argparse
+import json
+import os
+from os import path
+import shutil
+import subprocess
+import sys
+
+# Display the docstring if the user passes -h|--help
+argparse.ArgumentParser(description=__doc__).parse_args()
+
+SCRIPT_ROOT = path.dirname(path.realpath(__file__))
+REPO_ROOT = path.realpath(path.join(SCRIPT_ROOT, '..', '..', '..'))
+
+NUGET_SRC_DIR = path.join(REPO_ROOT, 'src', '.nuget')
+assert path.exists(NUGET_SRC_DIR), \
+    "Expected %s to exist; please check whether REPO_ROOT is really %s" % (NUGET_SRC_DIR, REPO_ROOT)
+
+ORIGIN_FILE = path.join(SCRIPT_ROOT, 'project.json')
+TARGET_FILE = path.join(NUGET_SRC_DIR, 'optdata', 'project.json')
+
+ARCH_LIST = ['x64', 'x86']
+TOOL_LIST = ['IBC', 'PGO']
+
+def get_buildos():
+    """Returns the Build_OS component used by the build system."""
+    if os.name == 'nt':
+        return 'Windows_NT'
+    else:
+        sysname = os.uname()[0]
+        return 'OSX' if sysname.lower() == 'Darwin'.lower() else sysname
+
+def get_optdata_version(tool):
+    """Returns the version string specified in project.json for the given tool."""
+    package_name = 'optimization.%s.CoreCLR' % (tool)
+    with open(ORIGIN_FILE) as json_file:
+        return json.load(json_file)['dependencies'][package_name]
+
+def get_optdata_dir(tool, arch):
+    """Returns an absolute path to the directory that should contain optdata given a tool,arch"""
+    package_name = 'optimization.%s-%s.%s.CoreCLR' % (get_buildos(), arch, tool)
+    package_version = get_optdata_version(tool)
+    return path.join(REPO_ROOT, 'packages', package_name, package_version, 'data')
+
+def check_for_unstaged_changes(file_path):
+    """Returns whether a file in git has untracked changes."""
+    if not path.exists(file_path):
+        return False
+    try:
+        subprocess.check_call(['git', 'diff', '--quiet', '--', file_path])
+        return False
+    except subprocess.CalledProcessError:
+        return True
+
+def main():
+    """Entry point"""
+    if check_for_unstaged_changes(TARGET_FILE):
+        print("ERROR: You seem to have unstaged changes to %s that would be overwritten."
+              % (TARGET_FILE))
+        print("Please clean, commit, or stash them before running this script.")
+        return 1
+
+    if not path.exists(path.dirname(TARGET_FILE)):
+        os.makedirs(path.dirname(TARGET_FILE))
+    shutil.copyfile(ORIGIN_FILE, TARGET_FILE)
+
+    print("Bootstrapping optdata is complete.")
+    for tool in TOOL_LIST:
+        for arch in ARCH_LIST:
+            optdata_dir = get_optdata_dir(tool, arch)
+            print("  * Copy %s %s files into: %s" % (arch, tool, optdata_dir))
+    print("NOTE: Make sure to add 'skiprestoreoptdata' as a switch on the build command line!")
+
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/tests/scripts/optdata/project.json b/tests/scripts/optdata/project.json
new file mode 100644 (file)
index 0000000..ae8f946
--- /dev/null
@@ -0,0 +1,12 @@
+{
+    "dependencies": {
+        "optimization.IBC.CoreCLR": "99.99.99-test",
+        "optimization.PGO.CoreCLR": "99.99.99-test"
+    },
+    "frameworks": {
+        "netstandard": {}
+    },
+    "runtimes": {
+        "win7-x64": {}
+    }
+}