<commit_before># Copyright (c) 2010-2017 the corto developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require "#{ENV['CORTO_BUILD']}/common"
require "#{ENV['CORTO_BUILD']}/libmapping"
require 'rake/clean'
require 'pathname'

CHDIR_SET = false if not defined? CHDIR_SET
if !CHDIR_SET then
  Dir.chdir(File.dirname(Rake.application.rakefile))
end

if not ENV['CORTO_BUILD'] then
  raise "CORTO_BUILD not defined (did you forget to 'source configure'?)"
end
if not defined? ARTEFACT then
  raise "artefact: ARTEFACT not specified\n"
end

# Private variables
TARGETDIR = ENV['CORTO_TARGET'] + "/lib" if not defined? TARGETDIR
GENERATED_SOURCES = [] if not defined? GENERATED_SOURCES
GENERATED_HEADERS = [] if not defined? GENERATED_HEADERS
USE_PACKAGE_LOADED = [] if not defined? USE_PACKAGE_LOADED

# Variable that indicates whether dependencies for redistributable librares are
# resolved
$redis_dependencies_resolved = true

ARTEFACT_NAME = getArtefactName(ARTEFACT_PREFIX, ARTEFACT, ARTEFACT_EXT)

# Add lib path for builds that don't install to global environment
if ENV['CORTO_TARGET'] != "/usr/local" then
  LIBPATH << "#{ENV['CORTO_TARGET']}/lib"
end

# Add default include paths
INCLUDE <<
  "src" <<
  "." <<
  "#{ENV['CORTO_TARGET']}/include/corto/#{CORTO_VERSION}" <<
  "#{ENV['CORTO_HOME']}/include/corto/#{CORTO_VERSION}" <<
  "/usr/local/include/corto/#{CORTO_VERSION}"

# Default CFLAGS
CFLAGS << "-std=c99" << "-fPIC" << "-D_XOPEN_SOURCE=600"
CFLAGS.unshift("-Wall")

# Default CXXFLAGS
CXXFLAGS << "-Wall" << "-std=c++0x" << "-fPIC" << "-Wno-write-strings"

if CORTO_OS == "Darwin" then
  LFLAGS << "-undefined dynamic_lookup"
  LFLAGS << "-undefined dynamic_lookup"
end

# Set default compiler
if LANGUAGE == "c" then
  COMPILER = CC
elsif LANGUAGE == "c4cpp" then
  COMPILER = CXX
elsif LANGUAGE == "cpp" then
  COMPILER = CXX
elsif LANGUAGE == "c++" then
  COMPILER = CXX
else
  abort "\033[1;31m[ language #{LANGUAGE} unsupported ]\033[0;49m"
end

# Set NDEBUG macro in release builds to disable tracing & checking
# Also enable optimizations
if CONFIG == "release" then
  CFLAGS << "-DNDEBUG" << "-O3"
  CXXFLAGS << "-DNDEBUG" << "-O3"
end

# Enable debug info, coverage and disable optimizations in debug
if CONFIG == "debug" then
  CFLAGS << "-g" << "-O0"
  CXXFLAGS << "-g" << "-O0"

  if COVERAGE == true then
    CFLAGS << "-fprofile-arcs" << "-ftest-coverage"
    CXXFLAGS << "-fprofile-arcs" << "-ftest-coverage"
    LFLAGS << "-fprofile-arcs" << "-ftest-coverage"
  end
end

# Crawl src directory to get list of source files
SOURCES = Rake::FileList["src/**/*.{c,cpp}"] - ALWAYS_REBUILD
OBJECTS = SOURCES.
          ext(".o").
          pathmap(".corto/%{^src/,obj/#{CORTO_PLATFORM}/}p") +
        Rake::FileList[GENERATED_SOURCES].
          ext(".o").
          pathmap(".corto/obj/#{CORTO_PLATFORM}/%f")

# Load packages from file
if File.exists? ".corto/packages.txt" then
  verbose(false)
  f = File.open(".corto/packages.txt")
  f.each_line do |l|
    p = l.strip
    USE_PACKAGE_LOADED.push p
    USE_PACKAGE.push p
  end
end

# Setup default clean & clobber rules
CLEAN.include(".corto/obj/#{CORTO_PLATFORM}")
CLEAN.include("doc")
if NOCORTO == false
  CLEAN.include("c")
end
CLEAN.include("*.gcov")
CLOBBER.include(".corto/obj")
CLOBBER.include(".corto/*.c")
if ARTEFACT_EXT and not ARTEFACT_EXT == "" then
  CLOBBER.include("./#{ARTEFACT_PREFIX}#{ARTEFACT}.#{ARTEFACT_EXT}")
  CLOBBER.include(TARGETDIR + "/" + "#{ARTEFACT_PREFIX}#{ARTEFACT}.#{ARTEFACT_EXT}")
else
  CLOBBER.include("./#{ARTEFACT_PREFIX}#{ARTEFACT}")
  CLOBBER.include(TARGETDIR + "/" + "#{ARTEFACT_PREFIX}#{ARTEFACT}")
end
CLOBBER.include(TARGETDIR + "/" + "#{ARTEFACT_PREFIX}#{ARTEFACT}.a")
CLOBBER.include(GENERATED_SOURCES)
CLOBBER.include(GENERATED_HEADERS)

if TARGET != "corto" then
  CLOBBER.include("include/_load.h")
  CLOBBER.include("include/_interface.h")
  CLOBBER.include("include/_project.h")
  CLOBBER.include("include/_api.h")
end

if File.exists? "project.json"
  CLOBBER.include("rakefile")
end

# If packages.txt is empty, clobber it
if USE_PACKAGE_LOADED.length == 0 then
  CLOBBER.include(".corto/packages.txt")
end

# --- BUILD RULES

# Task that collects all artefacts from the build and store them in a 'pack'
# folder, where they can be tarred for redistribution.
task :collect do
  verbose(VERBOSE)
  artefact = "#{TARGETDIR}/#{ARTEFACT}"
  target = ENV['HOME'] + "/.corto/pack" + artefact["#{ENV['CORTO_TARGET']}".length..artefact.length]
  targetDir = target.split("/")[0...-1].join("/")
  cmd "mkdir -p #{targetDir}"
  cmd "cp #{artefact} #{target}"

  if File.exists? "#{TARGETDIR}/build.cmd" then
    cmd "cp #{TARGETDIR}/build.cmd #{targetDir}/build.cmd"
  end
end

# Rule to automatically create packages.txt
file ".corto/packages.txt" do
  verbose(VERBOSE)
  cmd "mkdir -p .corto"
  cmd "touch .corto/packages.txt"
end

task :binary => ARTEFACT_NAME do
  UNINSTALL << TARGETDIR
end

def relative_path(from, to)
  from = Pathname.new from
  to = Pathname.new to
  begin
    to.relative_path_from from
  rescue
    to
  end
end

def package_from_path(directory, repo)
  package = nil
  env = ENV['CORTO_TARGET'] + repo + ENV['CORTO_VERSION']
  if directory[0..env.length - 1] == env then
    package = relative_path(env, directory)
  else
    env = "/usr/local" + repo + ENV['CORTO_VERSION']
    if directory[0..env.length - 1] == env then
      package = relative_path(env, directory)
    end
  end
  return package
end

def get_library_name(hardcodedPaths, link, directory, basename, prefix, ext)
  if directory != "." then
    directory = directory + "/"
  end
  if ext != "" then
    ext = "." + ext
  end

  if not hardcodedPaths then
    artefact = ""
    package = package_from_path(directory, "/lib/corto/")
    if not package then
      package = package_from_path(directory, "/bin/cortobin/")
      if package then
        artefact = "bin"
      end
    else
      artefact = "lib"
    end
    if package then
      basename = package.to_s.gsub("/", "_")
    end
    if link then
      directory = "-l"
      prefix = ""
      ext = ""
    else
      directory = ENV['CORTO_TARGET'] + "/redistr/corto/" + ENV['CORTO_VERSION'] + "/" + artefact + "/"
      sh "mkdir -p #{directory}"
    end
  end
  directory + prefix + basename + ext
end

def get_artefact_name(hardcodedPaths)
  result = get_library_name(hardcodedPaths, FALSE, TARGETDIR, ARTEFACT, ARTEFACT_PREFIX, ARTEFACT_EXT)
end

# Build artefact
def build_target(hardcodedPaths)
  verbose(VERBOSE)

  artefact = get_artefact_name(hardcodedPaths)

  if not File.exists? TARGETDIR then
    cmd "mkdir -p #{TARGETDIR}"
  end

  # Create list of files that are going to be linked with. Abstract away from the
  # difference between dll, so and dylib. When a dylib is encountered, perform  a
  # bit of magic to ensure that the executable can find the shared object.
  linked = LINK.map do |l|
    l = corto_replace(l)
    lib = get_library_name(hardcodedPaths, TRUE, File.dirname(l), File.basename(l), "lib", "so")
    if hardcodedPaths and (not File.exists? lib) and (CORTO_OS == "Darwin") then
      lib = get_library_name(hardcodedPaths, TRUE, File.dirname(l), File.basename(l), "lib", "dylib")
      if (not File.exists? lib) then
        abort "\033[1;31mcorto:\033[0;49m #{l} not found"
      end
    end
    lib
  end

  # If not using hardcoded paths, copy libraries that project specified in LINK
  # to redistributable directory
  if not hardcodedPaths and defined? LINK_NO_DEPS then
    LINK_NO_DEPS.each do |l|
      l = corto_replace(l)
      so = File.dirname(l) + "/lib" + File.basename(l) + ".so"
      targetDir = ENV['CORTO_TARGET'] + "/redistr/corto/" + ENV['CORTO_VERSION'] + "/lib"
      targetSo = targetDir + "/lib" + File.basename(l) + ".so"
      if File.exists? so and not File.exists? targetSo then
        sh "cp #{so} #{targetDir}"
      else
        so = File.dirname(l) + "/lib" + File.basename(l) + ".dylib"
        if File.exists? so then
          sh "cp #{so} #{targetDir}"
        end
      end
    end
  end

  objects = OBJECTS.clone

  # Build ALWAYS_REBUILD
  ALWAYS_REBUILD.each do |file|
    obj = file.ext(".o").pathmap(".corto/obj/#{CORTO_PLATFORM}/%f")
    if hardcodedPaths then
      build_source(file, obj, true, "-DCORTO_OBJECT_NAME='\"#{artefact}\"'")
    else
      if ARTEFACT_EXT and ARTEFACT_EXT != "" then
        build_source(file, obj, true, "-DCORTO_REDIS -DCORTO_OBJECT_NAME='\"#{ARTEFACT_PREFIX}#{ARTEFACT}.#{ARTEFACT_EXT}\"'")
      else
        build_source(file, obj, true, "-DCORTO_REDIS -DCORTO_OBJECT_NAME='\"#{ARTEFACT_PREFIX}#{ARTEFACT}\"'")
      end
    end
    objects << obj
  end

  objects  = "#{objects.to_a.uniq.join(' ')}"
  linked_objects = linked.join(' ')

  libpath = "#{LIBPATH.map {|i| "-L" + corto_replace(i)}.join(" ")} "
  libmapping = "#{(LibMapping.mapLibs(LIB)).map {|i| "-l" + i}.join(" ")}"
  lflags = "#{LFLAGS.join(" ")}"

  if not hardcodedPaths then
    libpath = libpath + " -L#{CORTO_TARGET}/redistr/corto/#{CORTO_VERSION}/lib"
  end

  linkShared = ""
  if ARTEFACT_EXT == "so" then
    linkShared = "--shared"
  end

  # Put lflags after objects so that linker knows about unresolved symbols
  # before processing libraries
  cc_command = "#{COMPILER} #{objects} #{libpath} #{linked_objects} #{lflags} #{libmapping} #{linkShared} -o #{artefact}"
  begin
    cmd cc_command
  rescue
    abort
  end

  # If required, alter paths to dylib files
  linked.each do |lib|
    if File.extname(lib) == ".dylib" then
      # Obtain name of library, check if its different from target path. If
      # names don't match, setup a task to alter the name in the executable
      # path.
      libname = `otool -L #{lib}`.split("\n")[1].split[0]
      if libname != lib then
        cmd "install_name_tool -change #{libname} #{lib} #{artefact}"
      end
    end
  end

  # If target is release, strip binary
  if CONFIG == "release" then
    cmd "strip -Sx #{artefact}"
  end
end

def build()
  verbose(VERBOSE)

  # Check if there were any new files created during code generation
  files = Rake::FileList["src/*.{c,cpp}"] - ALWAYS_REBUILD
  files.each do |file|
    obj = file.ext(".o").pathmap(".corto/obj/#{CORTO_PLATFORM}/%f")
    if not OBJECTS.include? obj
      build_source(file, obj, true, "")
      OBJECTS << obj
    end
  end

  if not ENV['binaries'] == "false" then
    if not LOCAL then
      if not ENV['redistr'] == "false" then
        if $redis_dependencies_resolved then
          build_target(false)
          if ENV['silent'] != "true" then
            msg "rds #{C_BOLD}#{relative_path(ENV['CORTO_TARGET'], get_artefact_name(FALSE))}"
          end
        else
          msg "rds #{C_WARNING}missing dependencies, skipping"
        end
      end
    end
    build_target(true)
    if ENV['silent'] != "true" then
      artefact = "???"
      if ARTEFACT_EXT == "so" then
        artefact = "pkg"
      else
        artefact = "app"
      end
      msg "#{artefact} #{C_BOLD}#{relative_path(ENV['CORTO_TARGET'], get_artefact_name(TRUE))}"
    end
  end
end

if MULTITHREAD == true then
  multitask ARTEFACT_NAME => OBJECTS do
    build()
  end
else
  task ARTEFACT_NAME => OBJECTS do
    build()
  end
end

def loadPackageConfigs()
  USE_PACKAGE.each do |p|
    location = `corto locate #{p} --path`.strip
    if not $?.to_i == 0 then
      if ENV['binaries'] == "true" then
        STDERR.puts "[\033[0;31m error\033[0;49m missing dependency: #{p} ]"
        sh "corto locate #{p} --verbose --error_only"
        abort
      else
        # If not building binaries, all we need is a build.rb
        location = "#{ENV['CORTO_TARGET']}/lib/corto/#{CORTO_VERSION}/#{p}"
        if not File.exists? location then
        STDERR.puts "[\033[0;31m error\033[0;49m missing dependency: #{p} ]"
          abort
        end
      end
    else
      redis_msg = ""

      # Check if redistributable library can be found for dependency
      if ENV['redistr'] != "false" then
        redistr = `corto locate #{p} --lib-redistr`.strip
        if not $?.to_i == 0 then
          # Disable redistributable build when there are unresolved dependencies
          redis_msg = "#{C_WARNING} (no redistr)#{C_NORMAL}"
          $redis_dependencies_resolved = false
        end
      end
      if ENV['silent'] != "true" then
        msg "use #{C_NORMAL}#{location}#{redis_msg}"
      end
    end

    if LANGUAGE == "c4cpp" then
      lang_buildscript = "#{location}/c/build.rb"
    else
      lang_buildscript = "#{location}/#{LANGUAGE}/build.rb"
    end
    if not NOCORTO and defined? LANGUAGE and LANGUAGE != "none" then
      if File.exists? lang_buildscript then
        require "#{lang_buildscript}"
      end
    end

    buildscript = location + "/build.rb"
    if File.exists? buildscript then
        require "#{buildscript}"
    end
  end
end

task :msg do
  if ENV['silent'] != "true" then
    pkg = relative_path(CORTO_BUILDROOT, Dir.pwd).to_s
    if pkg == "." then
      msg "#{C_HIGHLIGHT}build"
    else
      msg "#{C_HIGHLIGHT}build #{C_BOLD}#{pkg}"
    end
  end
end

task :prebuild => GENERATED_SOURCES do
  verbose(VERBOSE)

  # Load dependency build instructions before anything else
  loadPackageConfigs()

  # Don't overwhelm the compiler with potentially duplicate include paths that can
  # be introduced when the same package is included multiple times, by multiple
  # dependencies.
  INCLUDE.uniq!
end

task :postbuild

# The default rule that kicks of the build process
task :default => [:msg, :prebuild, :binary, :postbuild]

task :clean do
  verbose(VERBOSE)

  if ENV['silent'] != "true" then
    pkg = relative_path(CORTO_BUILDROOT, Dir.pwd).to_s
    if pkg == "." then
      msg "clean"
    else
      msg "clean #{C_NORMAL}#{pkg}"
    end
  end
end

# For make junkies
task :all => :default

# Build and run tests for project
task :test do
  verbose(VERBOSE)
  TEST = true

  # Build test
  if File.exists? "test/rakefile" then
    begin
      buildCmd = "none"
      if ENV['clean'] == "true" then
        buildCmd = "clobber"
      elsif ENV['rebuild'] == "true" then
        buildCmd = "clobber default"
      elsif ENV['build'] == "true" then
        buildCmd = ""
      end
      if buildCmd != "none" then
        cmd "rake #{buildCmd} -f test/rakefile silent=true"
      end
    rescue
      STDERR.puts "\033[1;31mcorto:\033[0;49m failed to build test"<commit_msg>#341 generate rakefile for test if it uses project.json<commit_after># Copyright (c) 2010-2017 the corto developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require "#{ENV['CORTO_BUILD']}/common"
require "#{ENV['CORTO_BUILD']}/libmapping"
require 'rake/clean'
require 'pathname'

CHDIR_SET = false if not defined? CHDIR_SET
if !CHDIR_SET then
  Dir.chdir(File.dirname(Rake.application.rakefile))
end

if not ENV['CORTO_BUILD'] then
  raise "CORTO_BUILD not defined (did you forget to 'source configure'?)"
end
if not defined? ARTEFACT then
  raise "artefact: ARTEFACT not specified\n"
end

# Private variables
TARGETDIR = ENV['CORTO_TARGET'] + "/lib" if not defined? TARGETDIR
GENERATED_SOURCES = [] if not defined? GENERATED_SOURCES
GENERATED_HEADERS = [] if not defined? GENERATED_HEADERS
USE_PACKAGE_LOADED = [] if not defined? USE_PACKAGE_LOADED

# Variable that indicates whether dependencies for redistributable librares are
# resolved
$redis_dependencies_resolved = true

ARTEFACT_NAME = getArtefactName(ARTEFACT_PREFIX, ARTEFACT, ARTEFACT_EXT)

# Add lib path for builds that don't install to global environment
if ENV['CORTO_TARGET'] != "/usr/local" then
  LIBPATH << "#{ENV['CORTO_TARGET']}/lib"
end

# Add default include paths
INCLUDE <<
  "src" <<
  "." <<
  "#{ENV['CORTO_TARGET']}/include/corto/#{CORTO_VERSION}" <<
  "#{ENV['CORTO_HOME']}/include/corto/#{CORTO_VERSION}" <<
  "/usr/local/include/corto/#{CORTO_VERSION}"

# Default CFLAGS
CFLAGS << "-std=c99" << "-fPIC" << "-D_XOPEN_SOURCE=600"
CFLAGS.unshift("-Wall")

# Default CXXFLAGS
CXXFLAGS << "-Wall" << "-std=c++0x" << "-fPIC" << "-Wno-write-strings"

if CORTO_OS == "Darwin" then
  LFLAGS << "-undefined dynamic_lookup"
  LFLAGS << "-undefined dynamic_lookup"
end

# Set default compiler
if LANGUAGE == "c" then
  COMPILER = CC
elsif LANGUAGE == "c4cpp" then
  COMPILER = CXX
elsif LANGUAGE == "cpp" then
  COMPILER = CXX
elsif LANGUAGE == "c++" then
  COMPILER = CXX
else
  abort "\033[1;31m[ language #{LANGUAGE} unsupported ]\033[0;49m"
end

# Set NDEBUG macro in release builds to disable tracing & checking
# Also enable optimizations
if CONFIG == "release" then
  CFLAGS << "-DNDEBUG" << "-O3"
  CXXFLAGS << "-DNDEBUG" << "-O3"
end

# Enable debug info, coverage and disable optimizations in debug
if CONFIG == "debug" then
  CFLAGS << "-g" << "-O0"
  CXXFLAGS << "-g" << "-O0"

  if COVERAGE == true then
    CFLAGS << "-fprofile-arcs" << "-ftest-coverage"
    CXXFLAGS << "-fprofile-arcs" << "-ftest-coverage"
    LFLAGS << "-fprofile-arcs" << "-ftest-coverage"
  end
end

# Crawl src directory to get list of source files
SOURCES = Rake::FileList["src/**/*.{c,cpp}"] - ALWAYS_REBUILD
OBJECTS = SOURCES.
          ext(".o").
          pathmap(".corto/%{^src/,obj/#{CORTO_PLATFORM}/}p") +
        Rake::FileList[GENERATED_SOURCES].
          ext(".o").
          pathmap(".corto/obj/#{CORTO_PLATFORM}/%f")

# Load packages from file
if File.exists? ".corto/packages.txt" then
  verbose(false)
  f = File.open(".corto/packages.txt")
  f.each_line do |l|
    p = l.strip
    USE_PACKAGE_LOADED.push p
    USE_PACKAGE.push p
  end
end

# Setup default clean & clobber rules
CLEAN.include(".corto/obj/#{CORTO_PLATFORM}")
CLEAN.include("doc")
if NOCORTO == false
  CLEAN.include("c")
end
CLEAN.include("*.gcov")
CLOBBER.include(".corto/obj")
CLOBBER.include(".corto/*.c")
if ARTEFACT_EXT and not ARTEFACT_EXT == "" then
  CLOBBER.include("./#{ARTEFACT_PREFIX}#{ARTEFACT}.#{ARTEFACT_EXT}")
  CLOBBER.include(TARGETDIR + "/" + "#{ARTEFACT_PREFIX}#{ARTEFACT}.#{ARTEFACT_EXT}")
else
  CLOBBER.include("./#{ARTEFACT_PREFIX}#{ARTEFACT}")
  CLOBBER.include(TARGETDIR + "/" + "#{ARTEFACT_PREFIX}#{ARTEFACT}")
end
CLOBBER.include(TARGETDIR + "/" + "#{ARTEFACT_PREFIX}#{ARTEFACT}.a")
CLOBBER.include(GENERATED_SOURCES)
CLOBBER.include(GENERATED_HEADERS)

if TARGET != "corto" then
  CLOBBER.include("include/_load.h")
  CLOBBER.include("include/_interface.h")
  CLOBBER.include("include/_project.h")
  CLOBBER.include("include/_api.h")
end

if File.exists? "project.json"
  CLOBBER.include("rakefile")
end

# If packages.txt is empty, clobber it
if USE_PACKAGE_LOADED.length == 0 then
  CLOBBER.include(".corto/packages.txt")
end

# --- BUILD RULES

# Task that collects all artefacts from the build and store them in a 'pack'
# folder, where they can be tarred for redistribution.
task :collect do
  verbose(VERBOSE)
  artefact = "#{TARGETDIR}/#{ARTEFACT}"
  target = ENV['HOME'] + "/.corto/pack" + artefact["#{ENV['CORTO_TARGET']}".length..artefact.length]
  targetDir = target.split("/")[0...-1].join("/")
  cmd "mkdir -p #{targetDir}"
  cmd "cp #{artefact} #{target}"

  if File.exists? "#{TARGETDIR}/build.cmd" then
    cmd "cp #{TARGETDIR}/build.cmd #{targetDir}/build.cmd"
  end
end

# Rule to automatically create packages.txt
file ".corto/packages.txt" do
  verbose(VERBOSE)
  cmd "mkdir -p .corto"
  cmd "touch .corto/packages.txt"
end

task :binary => ARTEFACT_NAME do
  UNINSTALL << TARGETDIR
end

def relative_path(from, to)
  from = Pathname.new from
  to = Pathname.new to
  begin
    to.relative_path_from from
  rescue
    to
  end
end

def package_from_path(directory, repo)
  package = nil
  env = ENV['CORTO_TARGET'] + repo + ENV['CORTO_VERSION']
  if directory[0..env.length - 1] == env then
    package = relative_path(env, directory)
  else
    env = "/usr/local" + repo + ENV['CORTO_VERSION']
    if directory[0..env.length - 1] == env then
      package = relative_path(env, directory)
    end
  end
  return package
end

def get_library_name(hardcodedPaths, link, directory, basename, prefix, ext)
  if directory != "." then
    directory = directory + "/"
  end
  if ext != "" then
    ext = "." + ext
  end

  if not hardcodedPaths then
    artefact = ""
    package = package_from_path(directory, "/lib/corto/")
    if not package then
      package = package_from_path(directory, "/bin/cortobin/")
      if package then
        artefact = "bin"
      end
    else
      artefact = "lib"
    end
    if package then
      basename = package.to_s.gsub("/", "_")
    end
    if link then
      directory = "-l"
      prefix = ""
      ext = ""
    else
      directory = ENV['CORTO_TARGET'] + "/redistr/corto/" + ENV['CORTO_VERSION'] + "/" + artefact + "/"
      sh "mkdir -p #{directory}"
    end
  end
  directory + prefix + basename + ext
end

def get_artefact_name(hardcodedPaths)
  result = get_library_name(hardcodedPaths, FALSE, TARGETDIR, ARTEFACT, ARTEFACT_PREFIX, ARTEFACT_EXT)
end

# Build artefact
def build_target(hardcodedPaths)
  verbose(VERBOSE)

  artefact = get_artefact_name(hardcodedPaths)

  if not File.exists? TARGETDIR then
    cmd "mkdir -p #{TARGETDIR}"
  end

  # Create list of files that are going to be linked with. Abstract away from the
  # difference between dll, so and dylib. When a dylib is encountered, perform  a
  # bit of magic to ensure that the executable can find the shared object.
  linked = LINK.map do |l|
    l = corto_replace(l)
    lib = get_library_name(hardcodedPaths, TRUE, File.dirname(l), File.basename(l), "lib", "so")
    if hardcodedPaths and (not File.exists? lib) and (CORTO_OS == "Darwin") then
      lib = get_library_name(hardcodedPaths, TRUE, File.dirname(l), File.basename(l), "lib", "dylib")
      if (not File.exists? lib) then
        abort "\033[1;31mcorto:\033[0;49m #{l} not found"
      end
    end
    lib
  end

  # If not using hardcoded paths, copy libraries that project specified in LINK
  # to redistributable directory
  if not hardcodedPaths and defined? LINK_NO_DEPS then
    LINK_NO_DEPS.each do |l|
      l = corto_replace(l)
      so = File.dirname(l) + "/lib" + File.basename(l) + ".so"
      targetDir = ENV['CORTO_TARGET'] + "/redistr/corto/" + ENV['CORTO_VERSION'] + "/lib"
      targetSo = targetDir + "/lib" + File.basename(l) + ".so"
      if File.exists? so and not File.exists? targetSo then
        sh "cp #{so} #{targetDir}"
      else
        so = File.dirname(l) + "/lib" + File.basename(l) + ".dylib"
        if File.exists? so then
          sh "cp #{so} #{targetDir}"
        end
      end
    end
  end

  objects = OBJECTS.clone

  # Build ALWAYS_REBUILD
  ALWAYS_REBUILD.each do |file|
    obj = file.ext(".o").pathmap(".corto/obj/#{CORTO_PLATFORM}/%f")
    if hardcodedPaths then
      build_source(file, obj, true, "-DCORTO_OBJECT_NAME='\"#{artefact}\"'")
    else
      if ARTEFACT_EXT and ARTEFACT_EXT != "" then
        build_source(file, obj, true, "-DCORTO_REDIS -DCORTO_OBJECT_NAME='\"#{ARTEFACT_PREFIX}#{ARTEFACT}.#{ARTEFACT_EXT}\"'")
      else
        build_source(file, obj, true, "-DCORTO_REDIS -DCORTO_OBJECT_NAME='\"#{ARTEFACT_PREFIX}#{ARTEFACT}\"'")
      end
    end
    objects << obj
  end

  objects  = "#{objects.to_a.uniq.join(' ')}"
  linked_objects = linked.join(' ')

  libpath = "#{LIBPATH.map {|i| "-L" + corto_replace(i)}.join(" ")} "
  libmapping = "#{(LibMapping.mapLibs(LIB)).map {|i| "-l" + i}.join(" ")}"
  lflags = "#{LFLAGS.join(" ")}"

  if not hardcodedPaths then
    libpath = libpath + " -L#{CORTO_TARGET}/redistr/corto/#{CORTO_VERSION}/lib"
  end

  linkShared = ""
  if ARTEFACT_EXT == "so" then
    linkShared = "--shared"
  end

  # Put lflags after objects so that linker knows about unresolved symbols
  # before processing libraries
  cc_command = "#{COMPILER} #{objects} #{libpath} #{linked_objects} #{lflags} #{libmapping} #{linkShared} -o #{artefact}"
  begin
    cmd cc_command
  rescue
    abort
  end

  # If required, alter paths to dylib files
  linked.each do |lib|
    if File.extname(lib) == ".dylib" then
      # Obtain name of library, check if its different from target path. If
      # names don't match, setup a task to alter the name in the executable
      # path.
      libname = `otool -L #{lib}`.split("\n")[1].split[0]
      if libname != lib then
        cmd "install_name_tool -change #{libname} #{lib} #{artefact}"
      end
    end
  end

  # If target is release, strip binary
  if CONFIG == "release" then
    cmd "strip -Sx #{artefact}"
  end
end

def build()
  verbose(VERBOSE)

  # Check if there were any new files created during code generation
  files = Rake::FileList["src/*.{c,cpp}"] - ALWAYS_REBUILD
  files.each do |file|
    obj = file.ext(".o").pathmap(".corto/obj/#{CORTO_PLATFORM}/%f")
    if not OBJECTS.include? obj
      build_source(file, obj, true, "")
      OBJECTS << obj
    end
  end

  if not ENV['binaries'] == "false" then
    if not LOCAL then
      if not ENV['redistr'] == "false" then
        if $redis_dependencies_resolved then
          build_target(false)
          if ENV['silent'] != "true" then
            msg "rds #{C_BOLD}#{relative_path(ENV['CORTO_TARGET'], get_artefact_name(FALSE))}"
          end
        else
          msg "rds #{C_WARNING}missing dependencies, skipping"
        end
      end
    end
    build_target(true)
    if ENV['silent'] != "true" then
      artefact = "???"
      if ARTEFACT_EXT == "so" then
        artefact = "pkg"
      else
        artefact = "app"
      end
      msg "#{artefact} #{C_BOLD}#{relative_path(ENV['CORTO_TARGET'], get_artefact_name(TRUE))}"
    end
  end
end

if MULTITHREAD == true then
  multitask ARTEFACT_NAME => OBJECTS do
    build()
  end
else
  task ARTEFACT_NAME => OBJECTS do
    build()
  end
end

def loadPackageConfigs()
  USE_PACKAGE.each do |p|
    location = `corto locate #{p} --path`.strip
    if not $?.to_i == 0 then
      if ENV['binaries'] == "true" then
        STDERR.puts "[\033[0;31m error\033[0;49m missing dependency: #{p} ]"
        sh "corto locate #{p} --verbose --error_only"
        abort
      else
        # If not building binaries, all we need is a build.rb
        location = "#{ENV['CORTO_TARGET']}/lib/corto/#{CORTO_VERSION}/#{p}"
        if not File.exists? location then
        STDERR.puts "[\033[0;31m error\033[0;49m missing dependency: #{p} ]"
          abort
        end
      end
    else
      redis_msg = ""

      # Check if redistributable library can be found for dependency
      if ENV['redistr'] != "false" then
        redistr = `corto locate #{p} --lib-redistr`.strip
        if not $?.to_i == 0 then
          # Disable redistributable build when there are unresolved dependencies
          redis_msg = "#{C_WARNING} (no redistr)#{C_NORMAL}"
          $redis_dependencies_resolved = false
        end
      end
      if ENV['silent'] != "true" then
        msg "use #{C_NORMAL}#{location}#{redis_msg}"
      end
    end

    if LANGUAGE == "c4cpp" then
      lang_buildscript = "#{location}/c/build.rb"
    else
      lang_buildscript = "#{location}/#{LANGUAGE}/build.rb"
    end
    if not NOCORTO and defined? LANGUAGE and LANGUAGE != "none" then
      if File.exists? lang_buildscript then
        require "#{lang_buildscript}"
      end
    end

    buildscript = location + "/build.rb"
    if File.exists? buildscript then
        require "#{buildscript}"
    end
  end
end

task :msg do
  if ENV['silent'] != "true" then
    pkg = relative_path(CORTO_BUILDROOT, Dir.pwd).to_s
    if pkg == "." then
      msg "#{C_HIGHLIGHT}build"
    else
      msg "#{C_HIGHLIGHT}build #{C_BOLD}#{pkg}"
    end
  end
end

task :prebuild => GENERATED_SOURCES do
  verbose(VERBOSE)

  # Load dependency build instructions before anything else
  loadPackageConfigs()

  # Don't overwhelm the compiler with potentially duplicate include paths that can
  # be introduced when the same package is included multiple times, by multiple
  # dependencies.
  INCLUDE.uniq!
end

task :postbuild

# The default rule that kicks of the build process
task :default => [:msg, :prebuild, :binary, :postbuild]

task :clean do
  verbose(VERBOSE)

  if ENV['silent'] != "true" then
    pkg = relative_path(CORTO_BUILDROOT, Dir.pwd).to_s
    if pkg == "." then
      msg "clean"
    else
      msg "clean #{C_NORMAL}#{pkg}"
    end
  end
end

# For make junkies
task :all => :default

# Build and run tests for project
task :test do
  verbose(VERBOSE)
  TEST = true

  # Build test
  if File.exists? "test/project.json" then
    cmd "corto rakefile test"
  end

  if File.exists? "test/rakefile" then
    begin
      buildCmd = "none"
      if ENV['clean'] == "true" then
        buildCmd = "clobber"
      elsif ENV['rebuild'] == "true" then
        buildCmd = "clobber default"
      elsif ENV['build'] == "true" then
        buildCmd = ""
      end
      if buildCmd != "none" then
        cmd "rake #{buildCmd} -f test/rakefile silent=true"
      end
    rescue
      STDERR.puts "\033[1;31mcorto:\033[0;49m failed to build test"
{'fraction_non_alphanumeric': 0.13113695090439276, 'fraction_numerical': 0.005975452196382429, 'mean_word_length': 3.2852595155709343, 'pattern_counts': {'":': 0, '<': 131, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '461144', 'n_tokens_mistral': 10959, 'n_tokens_neox': 10643, 'n_words': 2982}