blob: e61f7bfa00006a719554988740cc1e7d03662090 [file] [log] [blame]
#!src/build/run_python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Overlays files from mods over third_party as an output staging directory.
This can be used either as a python module or as a standalone script. The
latter can be helpful in debugging staging or in quickly running staging when
you know running all of configure is unnecessary.
Code to create out/staging of properly overlaid files. All files are created
as symlinks.
"""
import os
import subprocess
import sys
from src.build import build_common
from src.build import build_options
from src.build.util import file_util
_GIT_DIR = '.git'
_SRC_DIR = 'src'
_MODS_DIR = 'mods'
_THIRD_PARTY_DIR = 'third_party'
_INTERNAL_MODS_PATH = 'internal/mods'
_INTERNAL_THIRD_PARTY_PATH = 'internal/third_party'
TESTS_BASE_PATH = 'src/build/tests/analyze_diffs'
TESTS_MODS_PATH = os.path.join(TESTS_BASE_PATH, 'mods')
TESTS_THIRD_PARTY_PATH = os.path.join(TESTS_BASE_PATH, 'third_party')
def is_in_staging(input_path):
"""Does this input path look like one that should come from staging.
Examples are src/*, android/*, libyuv/*, chromium-ppapi/*.
"""
top_level = input_path.split(os.path.sep)[0]
return (top_level == 'src' or
os.path.exists(os.path.join('third_party', top_level)) or
os.path.exists(os.path.join('mods', top_level)))
def get_default_tracking_path(our_path):
"""Returns the tracking path for a given path.
This is not definitive, and is purely meant to be the default fall-back path
if the file does not have a FILE_TRACK_TAG explicitly identifying what it is
based on.
Returns None if there is no mapping from the input path to a upstream source
path."""
tracking_path = None
if our_path.startswith('mods/'):
rel_path = os.path.relpath(our_path, 'mods')
tracking_path = os.path.join('third_party', rel_path)
elif our_path.startswith(_INTERNAL_MODS_PATH):
rel_path = os.path.relpath(our_path, _INTERNAL_MODS_PATH)
tracking_path = os.path.join(_INTERNAL_THIRD_PARTY_PATH, rel_path)
elif our_path.startswith(TESTS_MODS_PATH):
rel_path = os.path.relpath(our_path, TESTS_MODS_PATH)
tracking_path = os.path.join(TESTS_BASE_PATH, 'third_party', rel_path)
return tracking_path
def get_composite_paths(staging_path):
if not staging_path.startswith(build_common.get_staging_root()):
return None, None
rel_path = os.path.relpath(staging_path, build_common.get_staging_root())
return (os.path.join('third_party', rel_path),
os.path.join('mods', rel_path))
def as_staging(input_path, always_stage=False):
"""Convert an input path to a staging path.
example input: android/frameworks/base/...
example staging: STAGING_ROOT/android/frameworks/base/...
"""
if always_stage or is_in_staging(input_path):
return os.path.join(build_common.get_staging_root(), input_path)
else:
return input_path
def as_real_path(input_path):
"""Convert an input path to a real path.
example input: android/frameworks/base/...
example real path: mods/android/frameworks/base/...
"""
path = os.path.realpath(as_staging(input_path))
return os.path.relpath(path, build_common.get_arc_root())
def third_party_to_staging(path):
"""Convert a third party path to a staging path.
example input: third_party/android/frameworks/base/...
example staging: STAGING_ROOT/android/frameworks/base/...
example input: internal/third_party/android/frameworks/base/...
example staging: STAGING_ROOT/android/frameworks/base/...
When an input path is not in third party directory, the path is returned
as-is.
"""
if path.startswith(_THIRD_PARTY_DIR):
return as_staging(os.path.relpath(path, _THIRD_PARTY_DIR))
elif path.startswith(_INTERNAL_THIRD_PARTY_PATH):
return as_staging(os.path.relpath(path, _INTERNAL_THIRD_PARTY_PATH))
return path
def _create_symlink(src_path, dest_dir):
"""Creates a symlink pointing to src_path in dest_dir with the same name."""
os.symlink(os.path.relpath(src_path, dest_dir),
os.path.join(dest_dir, os.path.basename(src_path)))
def _create_overlay_base(base_dir, overlays, dest_dir):
"""Creates symlinks to files and directories in base_dir.
This is a helper of _create_symlink_tree(). it creates symlinks to files and
directories in base_dir, except ones in overlays, into dest_dir.
"overlays" is a list of file and directory basenames in the overlay directory
corresponding to the given base_dir.
"""
def relevant(name):
if base_dir != 'third_party/chromium-ppapi':
return True
# Do not create symlinks in out/staging/chromium-ppapi/ except
# the whitelisted ones below.
# - ppapi: We need this to build libchromium_ppapi.so. Note that the files
# we need under ppapi/ do not depend on base/.
# - breakpad: minidump_generator.cc includes headers in the directory.
# Note that chromium_org/ does not have breakpad/.
# - third_party: ppapi/generators/ library depeneds on ply library in
# third_party directory.
# Note: Never add 'base' to |whitelist|. Having two base/ directories (one
# in out/staging/chromium-ppapi/ and the other in
# out/staging/android/external/chromium_org/) makes ARC build fail.
whitelist = ['ppapi', 'breakpad', 'third_party']
if name in whitelist:
return True
return False
# If there is no directory at base_dir, it means a new directory is
# introduced under the corresponding path in mods_root of
# _create_symlink_tree(). Skip it.
if not os.path.lexists(base_dir):
return
for name in os.listdir(base_dir):
if not relevant(name):
continue
if name == _GIT_DIR or name in overlays:
continue
_create_symlink(os.path.join(base_dir, name), dest_dir)
def _create_symlink_tree(mods_root, third_party_root, staging_root):
"""Creates a symlink tree of mods_root overlaid on third_party_root.
This method creates the symlink tree of mods_root directory (working as
same as recursive copy, but all files are symlinked instead of actual file
copy).
If third_party_root is given, each created directory is overlaid on the
corresponding directory in third_party_root (if exists).
For example:
Suppose mods_root is "mods/", third_party_root is "third_party/" and
staging_root is "out/staging/", then the symlink tree of mods/android/...
will be created at out/staging/android/..., with overlaying
third_party/android/...
"""
staging_root_parent = os.path.dirname(staging_root)
file_util.makedirs_safely(staging_root_parent)
if os.path.exists('mods/chromium-ppapi/base'):
# See comments in _create_overlay_base.
raise Exception('Putting headers in mods/chromium-ppapi/base will '
'cause code in chromium_org libbase implementation to '
'include headers from chromium-ppapi libbase and will '
'result in compilation errors or worse.')
for dirpath, dirs, fnames in os.walk(mods_root):
# Do not track .git directory.
if _GIT_DIR in dirs:
dirs.remove(_GIT_DIR)
relpath = os.path.relpath(dirpath, mods_root)
dest_dir = os.path.normpath(os.path.join(staging_root, relpath))
if not os.path.exists(dest_dir):
os.mkdir(dest_dir)
# Create symlinks for files.
for name in fnames:
_create_symlink(os.path.join(dirpath, name), dest_dir)
if third_party_root:
_create_overlay_base(
os.path.join(third_party_root, relpath), dirs + fnames, dest_dir)
def _get_link_targets(root):
link_target_map = {}
for dirpath, dirs, fnames in os.walk(root):
for name in fnames:
link_path = os.path.join(dirpath, name)
# The target of the symbolic link may have been already removed,
# so we must not check os.path.isfile(link_path) here.
if os.path.islink(link_path):
link_target_map[link_path] = os.readlink(link_path)
return link_target_map
def create_staging():
timer = build_common.SimpleTimer()
timer.start('Staging source files', True)
staging_root = build_common.get_staging_root()
# Store where all the old staging links pointed so we can compare after.
old_staging_links = _get_link_targets(staging_root)
if os.path.lexists(staging_root):
file_util.rmtree(staging_root)
_create_symlink_tree(_MODS_DIR, _THIRD_PARTY_DIR, staging_root)
# internal/ is an optional checkout
if build_options.OPTIONS.internal_apks_source_is_internal():
assert build_common.has_internal_checkout()
for name in os.listdir(_INTERNAL_THIRD_PARTY_PATH):
if os.path.exists(os.path.join(_THIRD_PARTY_DIR, name)):
raise Exception('Name conflict between internal/third_party and '
'third_party: ' + name)
_create_symlink_tree(_INTERNAL_MODS_PATH, _INTERNAL_THIRD_PARTY_PATH,
staging_root)
subprocess.check_call('internal/build/fix_staging.py')
# src/ is not overlaid on any directory.
_create_symlink_tree(_SRC_DIR, None, os.path.join(staging_root, 'src'))
# Update modification time for files that do not point to the same location
# that they pointed to in the previous tree to make sure they are built.
if old_staging_links:
new_staging_links = _get_link_targets(staging_root)
# Every file (not directory) under staging is in either one of following
# two states:
#
# F. The file itself is a symbolic link to a file under third_party or
# mods.
# D. Some ancestor directory is a symbolic link to a directory under
# third_party. (It is important that we do not create symbolic links to
# directories under mods)
#
# Let us say a file falls under "X-Y" case if it was in state X before
# re-staging and now in state Y. For all 4 possible cases, we can check if
# the actual destination of the file changed or not in the following way:
#
# F-F: We can just compare the target of the link.
# F-D, D-F: The target may have changed, but it needs some complicated
# computation to check. We treat them as changed to be conservative.
# D-D: We can leave it as-is since both point third_party.
#
# So we want to visit all files in state F either in old staging or new
# staging. For this purpose we can iterate through |*_staging_links| as
# they contain all files in state F.
#
# Note that |*_staging_links| may contain directory symbolic links, but
# it is okay to visit them too because directory timestamps do not matter.
# Actually excluding directory symbolic links from |old_staging_links| is
# difficult because link targets might be already removed.
for path in set(list(old_staging_links) + list(new_staging_links)):
if path in old_staging_links and path in new_staging_links:
should_touch = old_staging_links[path] != new_staging_links[path]
else:
should_touch = True
if should_touch and os.path.exists(path):
os.utime(path, None)
timer.done()
return True
if __name__ == '__main__':
build_options.OPTIONS.parse_configure_file()
sys.exit(not create_staging())