# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import glob
import os
import unittest
from src.test.py.bazel import test_base


class BazelWindowsCppTest(test_base.TestBase):

  def createProjectFiles(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'package(',
        '  default_visibility = ["//visibility:public"],',
        '  features=["windows_export_all_symbols"]',
        ')',
        '',
        'cc_library(',
        '  name = "A",',
        '  srcs = ["a.cc"],',
        '  hdrs = ["a.h"],',
        '  copts = ["/DCOMPILING_A_DLL"],',
        '  features = ["no_windows_export_all_symbols"],',
        ')',
        '',
        'cc_library(',
        '  name = "B",',
        '  srcs = ["b.cc"],',
        '  hdrs = ["b.h"],',
        '  deps = [":A"],',
        '  copts = ["/DNO_DLLEXPORT"],',
        ')',
        '',
        'cc_binary(',
        '  name = "C",',
        '  srcs = ["c.cc"],',
        '  deps = [":A", ":B" ],',
        '  linkstatic = 0,',
        ')',
    ])

    self.ScratchFile('a.cc', [
        '#include <stdio.h>',
        '#include "a.h"',
        'int a = 0;',
        'void hello_A() {',
        '  a++;',
        '  printf("Hello A, %d\\n", a);',
        '}',
    ])

    self.ScratchFile('b.cc', [
        '#include <stdio.h>',
        '#include "a.h"',
        '#include "b.h"',
        'void hello_B() {',
        '  hello_A();',
        '  printf("Hello B\\n");',
        '}',
    ])
    header_temp = [
        '#ifndef %{name}_H',
        '#define %{name}_H',
        '',
        '#if NO_DLLEXPORT',
        '  #define DLLEXPORT',
        '#elif COMPILING_%{name}_DLL',
        '  #define DLLEXPORT __declspec(dllexport)',
        '#else',
        '  #define DLLEXPORT __declspec(dllimport)',
        '#endif',
        '',
        'DLLEXPORT void hello_%{name}();',
        '',
        '#endif',
    ]
    self.ScratchFile('a.h',
                     [line.replace('%{name}', 'A') for line in header_temp])
    self.ScratchFile('b.h',
                     [line.replace('%{name}', 'B') for line in header_temp])

    c_cc_content = [
        '#include <stdio.h>',
        '#include "a.h"',
        '#include "b.h"',
        '',
        'void hello_C() {',
        '  hello_A();',
        '  hello_B();',
        '  printf("Hello C\\n");',
        '}',
        '',
        'int main() {',
        '  hello_C();',
        '  return 0;',
        '}',
    ]

    self.ScratchFile('c.cc', c_cc_content)

    self.ScratchFile('lib/BUILD', [
        'cc_library(',
        '  name = "A",',
        '  srcs = ["dummy.cc"],',
        '  features = ["windows_export_all_symbols"],',
        '  visibility = ["//visibility:public"],',
        ')',
    ])
    self.ScratchFile('lib/dummy.cc', ['void dummy() {}'])

    self.ScratchFile('main/main.cc', c_cc_content)

  def getBazelInfo(self, info_key):
    exit_code, stdout, stderr = self.RunBazel(['info', info_key])
    self.AssertExitCode(exit_code, 0, stderr)
    return stdout[0]

  def testBuildDynamicLibraryWithUserExportedSymbol(self):
    self.createProjectFiles()
    bazel_bin = self.getBazelInfo('bazel-bin')

    # //:A export symbols by itself using __declspec(dllexport), so it doesn't
    # need Bazel to export symbols using DEF file.
    exit_code, _, stderr = self.RunBazel(
        ['build', '//:A', '--output_groups=dynamic_library'])
    self.AssertExitCode(exit_code, 0, stderr)

    # TODO(pcloudy): change suffixes to .lib and .dll after making DLL
    # extensions correct on Windows.
    import_library = os.path.join(bazel_bin, 'A.if.lib')
    shared_library = os.path.join(bazel_bin, 'A_0.dll')
    empty_def_file = os.path.join(bazel_bin, 'A.gen.empty.def')

    self.assertTrue(os.path.exists(import_library))
    self.assertTrue(os.path.exists(shared_library))
    # An empty DEF file should be generated for //:A
    self.assertTrue(os.path.exists(empty_def_file))

  def testBuildDynamicLibraryWithExportSymbolFeature(self):
    self.createProjectFiles()
    bazel_bin = self.getBazelInfo('bazel-bin')

    # //:B doesn't export symbols by itself, so it need Bazel to export symbols
    # using DEF file.
    exit_code, _, stderr = self.RunBazel(
        ['build', '//:B', '--output_groups=dynamic_library'])
    self.AssertExitCode(exit_code, 0, stderr)

    # TODO(pcloudy): change suffixes to .lib and .dll after making DLL
    # extensions correct on Windows.
    import_library = os.path.join(bazel_bin, 'B.if.lib')
    shared_library = os.path.join(bazel_bin, 'B_0.dll')
    def_file = os.path.join(bazel_bin, 'B.gen.def')
    self.assertTrue(os.path.exists(import_library))
    self.assertTrue(os.path.exists(shared_library))
    # DEF file should be generated for //:B
    self.assertTrue(os.path.exists(def_file))

    # Test build //:B if windows_export_all_symbols feature is disabled by
    # no_windows_export_all_symbols.
    exit_code, _, stderr = self.RunBazel([
        'build', '//:B', '--output_groups=dynamic_library',
        '--features=no_windows_export_all_symbols'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    import_library = os.path.join(bazel_bin, 'B.if.lib')
    shared_library = os.path.join(bazel_bin, 'B_0.dll')
    empty_def_file = os.path.join(bazel_bin, 'B.gen.empty.def')
    self.assertTrue(os.path.exists(import_library))
    self.assertTrue(os.path.exists(shared_library))
    # An empty DEF file should be generated for //:B
    self.assertTrue(os.path.exists(empty_def_file))
    self.AssertFileContentNotContains(empty_def_file, 'hello_B')

  def testBuildCcBinaryWithDependenciesDynamicallyLinked(self):
    self.createProjectFiles()
    bazel_bin = self.getBazelInfo('bazel-bin')

    # Since linkstatic=0 is specified for //:C, it's dependencies should be
    # dynamically linked.
    exit_code, _, stderr = self.RunBazel(['build', '//:C'])
    self.AssertExitCode(exit_code, 0, stderr)

    # TODO(pcloudy): change suffixes to .lib and .dll after making DLL
    # extensions correct on
    # Windows.
    # a_import_library
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'A.if.lib')))
    # a_shared_library
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'A_0.dll')))
    # a_def_file
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'A.gen.empty.def')))
    # b_import_library
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'B.if.lib')))
    # b_shared_library
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'B_0.dll')))
    # b_def_file
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'B.gen.def')))
    # c_exe
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'C.exe')))

  def testBuildCcBinaryFromDifferentPackage(self):
    self.createProjectFiles()
    self.ScratchFile('main/BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        '  deps = ["//:B"],',
        '  linkstatic = 0,'
        ')',
    ])
    bazel_bin = self.getBazelInfo('bazel-bin')

    exit_code, _, stderr = self.RunBazel(['build', '//main:main'])
    self.AssertExitCode(exit_code, 0, stderr)

    # Test if A.dll and B.dll are copied to the directory of main.exe
    main_bin = os.path.join(bazel_bin, 'main/main.exe')
    self.assertTrue(os.path.exists(main_bin))
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'main/A_0.dll')))
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'main/B_0.dll')))

    # Run the binary to see if it runs successfully
    exit_code, stdout, stderr = self.RunProgram([main_bin])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertEqual(['Hello A, 1', 'Hello A, 2', 'Hello B', 'Hello C'], stdout)

  def testBuildCcBinaryDependsOnConflictDLLs(self):
    self.createProjectFiles()
    self.ScratchFile(
        'main/BUILD',
        [
            'cc_binary(',
            '  name = "main",',
            '  srcs = ["main.cc"],',
            '  deps = ["//:B", "//lib:A"],',  # Transitively depends on //:A
            '  linkstatic = 0,'
            ')',
        ])
    bazel_bin = self.getBazelInfo('bazel-bin')

    # //main:main depends on both //lib:A and //:A
    exit_code, _, stderr = self.RunBazel(['build', '//main:main'])
    self.AssertExitCode(exit_code, 0, stderr)

    # Run the binary to see if it runs successfully
    main_bin = os.path.join(bazel_bin, 'main/main.exe')
    exit_code, stdout, stderr = self.RunProgram([main_bin])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertEqual(['Hello A, 1', 'Hello A, 2', 'Hello B', 'Hello C'], stdout)
    # There are 2 A_{hash}.dll since //main:main depends on both //lib:A and
    # //:A
    self.assertEqual(
        len(glob.glob(os.path.join(bazel_bin, 'main', 'A_*.dll'))), 2)
    # There is only 1 B_{hash}.dll
    self.assertEqual(
        len(glob.glob(os.path.join(bazel_bin, 'main', 'B_*.dll'))), 1)

  def testBuildDifferentCcBinariesDependOnConflictDLLs(self):
    self.createProjectFiles()
    self.ScratchFile(
        'main/BUILD',
        [
            'cc_binary(',
            '  name = "main",',
            '  srcs = ["main.cc"],',
            '  deps = ["//:B"],',  # Transitively depends on //:A
            '  linkstatic = 0,'
            ')',
            '',
            'cc_binary(',
            '  name = "other_main",',
            '  srcs = ["other_main.cc"],',
            '  deps = ["//lib:A"],',
            '  linkstatic = 0,'
            ')',
        ])
    bazel_bin = self.getBazelInfo('bazel-bin')
    self.ScratchFile('main/other_main.cc', ['int main() {return 0;}'])

    # Building //main:main should succeed
    exit_code, _, stderr = self.RunBazel(
        ['build', '//main:main', '--incompatible_avoid_conflict_dlls'])
    self.AssertExitCode(exit_code, 0, stderr)
    main_bin = os.path.join(bazel_bin, 'main/main.exe')

    # Run the main_bin binary to see if it runs successfully
    exit_code, stdout, stderr = self.RunProgram([main_bin])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertEqual(['Hello A, 1', 'Hello A, 2', 'Hello B', 'Hello C'], stdout)
    # There is only 1 A_{hash}.dll since //main:main depends transitively on
    # //:A
    self.assertEqual(
        len(glob.glob(os.path.join(bazel_bin, 'main', 'A_*.dll'))), 1)
    # There is only 1 B_{hash}.dll
    self.assertEqual(
        len(glob.glob(os.path.join(bazel_bin, 'main', 'B_*.dll'))), 1)

    # Building //main:other_main should succeed
    exit_code, _, stderr = self.RunBazel([
        'build', '//main:main', '//main:other_main',
        '--incompatible_avoid_conflict_dlls'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    other_main_bin = os.path.join(bazel_bin, 'main/other_main.exe')

    # Run the other_main_bin binary to see if it runs successfully
    exit_code, stdout, stderr = self.RunProgram([other_main_bin])
    self.AssertExitCode(exit_code, 0, stderr)
    # There are 2 A_{hash}.dll since //main:main depends on //:A
    # and //main:other_main depends on //lib:A
    self.assertEqual(
        len(glob.glob(os.path.join(bazel_bin, 'main', 'A_*.dll'))), 2)

  def testDLLIsCopiedFromExternalRepo(self):
    self.ScratchFile('ext_repo/WORKSPACE')
    self.ScratchFile('ext_repo/BUILD', [
        'cc_library(',
        '  name = "A",',
        '  srcs = ["a.cc"],',
        '  features = ["windows_export_all_symbols"],',
        '  visibility = ["//visibility:public"],',
        ')',
    ])
    self.ScratchFile('ext_repo/a.cc', [
        '#include <stdio.h>',
        'void hello_A() {',
        '  printf("Hello A\\n");',
        '}',
    ])
    self.ScratchFile('WORKSPACE', [
        'local_repository(',
        '  name = "ext_repo",',
        '  path = "ext_repo",',
        ')',
    ])
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        '  deps = ["@ext_repo//:A"],',
        '  linkstatic = 0,',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'extern void hello_A();',
        '',
        'int main() {',
        '  hello_A();',
        '  return 0;',
        '}',
    ])

    bazel_bin = self.getBazelInfo('bazel-bin')

    exit_code, _, stderr = self.RunBazel(['build', '//:main', '-s'])
    self.AssertExitCode(exit_code, 0, stderr)

    # Test if A.dll is copied to the directory of main.exe
    main_bin = os.path.join(bazel_bin, 'main.exe')
    self.assertTrue(os.path.exists(main_bin))
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'A_9324b6d0.dll')))

    # Run the binary to see if it runs successfully
    exit_code, stdout, stderr = self.RunProgram([main_bin])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertEqual(['Hello A'], stdout)

  def testDynamicLinkingMSVCRT(self):
    self.createProjectFiles()
    bazel_output = self.getBazelInfo('output_path')

    # By default, it should link to msvcrt dynamically.
    exit_code, _, stderr = self.RunBazel(
        ['build', '//:A', '--output_groups=dynamic_library', '-s'])
    paramfile = os.path.join(bazel_output,
                             'x64_windows-fastbuild/bin/A_0.dll-2.params')
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('/MD', ''.join(stderr))
    self.AssertFileContentContains(paramfile, '/DEFAULTLIB:msvcrt.lib')
    self.assertNotIn('/MT', ''.join(stderr))
    self.AssertFileContentNotContains(paramfile, '/DEFAULTLIB:libcmt.lib')

    # Test build in debug mode.
    exit_code, _, stderr = self.RunBazel(
        ['build', '-c', 'dbg', '//:A', '--output_groups=dynamic_library', '-s'])
    paramfile = os.path.join(bazel_output,
                             'x64_windows-dbg/bin/A_0.dll-2.params')
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('/MDd', ''.join(stderr))
    self.AssertFileContentContains(paramfile, '/DEFAULTLIB:msvcrtd.lib')
    self.assertNotIn('/MTd', ''.join(stderr))
    self.AssertFileContentNotContains(paramfile, '/DEFAULTLIB:libcmtd.lib')

  def testStaticLinkingMSVCRT(self):
    self.createProjectFiles()
    bazel_output = self.getBazelInfo('output_path')

    # With static_link_msvcrt feature, it should link to msvcrt statically.
    exit_code, _, stderr = self.RunBazel([
        'build', '//:A', '--output_groups=dynamic_library',
        '--features=static_link_msvcrt', '-s'
    ])
    paramfile = os.path.join(bazel_output,
                             'x64_windows-fastbuild/bin/A_0.dll-2.params')
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertNotIn('/MD', ''.join(stderr))
    self.AssertFileContentNotContains(paramfile, '/DEFAULTLIB:msvcrt.lib')
    self.assertIn('/MT', ''.join(stderr))
    self.AssertFileContentContains(paramfile, '/DEFAULTLIB:libcmt.lib')

    # Test build in debug mode.
    exit_code, _, stderr = self.RunBazel([
        'build', '-c', 'dbg', '//:A', '--output_groups=dynamic_library',
        '--features=static_link_msvcrt', '-s'
    ])
    paramfile = os.path.join(bazel_output,
                             'x64_windows-dbg/bin/A_0.dll-2.params')
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertNotIn('/MDd', ''.join(stderr))
    self.AssertFileContentNotContains(paramfile, '/DEFAULTLIB:msvcrtd.lib')
    self.assertIn('/MTd', ''.join(stderr))
    self.AssertFileContentContains(paramfile, '/DEFAULTLIB:libcmtd.lib')

  def testBuildSharedLibraryFromCcBinaryWithStaticLink(self):
    self.createProjectFiles()
    self.ScratchFile(
        'main/BUILD',
        [
            'cc_binary(',
            '  name = "main.dll",',
            '  srcs = ["main.cc"],',
            '  deps = ["//:B"],',  # Transitively depends on //:A
            '  linkstatic = 1,'
            '  linkshared = 1,'
            '  features=["windows_export_all_symbols"]',
            ')',
        ])
    bazel_bin = self.getBazelInfo('bazel-bin')

    exit_code, _, stderr = self.RunBazel([
        'build', '//main:main.dll',
        '--output_groups=default,runtime_dynamic_libraries,interface_library'
    ])
    self.AssertExitCode(exit_code, 0, stderr)

    main_library = os.path.join(bazel_bin, 'main/main.dll')
    main_interface = os.path.join(bazel_bin, 'main/main.dll.if.lib')
    def_file = os.path.join(bazel_bin, 'main/main.dll.gen.def')
    self.assertTrue(os.path.exists(main_library))
    self.assertTrue(os.path.exists(main_interface))
    self.assertTrue(os.path.exists(def_file))
    # A.dll and B.dll should not be copied.
    self.assertFalse(os.path.exists(os.path.join(bazel_bin, 'main/A.dll')))
    self.assertFalse(os.path.exists(os.path.join(bazel_bin, 'main/B.dll')))
    self.AssertFileContentContains(def_file, 'hello_A')
    self.AssertFileContentContains(def_file, 'hello_B')
    self.AssertFileContentContains(def_file, 'hello_C')

  def testBuildSharedLibraryFromCcBinaryWithDynamicLink(self):
    self.createProjectFiles()
    self.ScratchFile(
        'main/BUILD',
        [
            'cc_binary(',
            '  name = "main.dll",',
            '  srcs = ["main.cc"],',
            '  deps = ["//:B"],',  # Transitively depends on //:A
            '  linkstatic = 0,'
            '  linkshared = 1,'
            '  features=["windows_export_all_symbols"]',
            ')',
            '',
            'genrule(',
            '  name = "renamed_main",',
            '  srcs = [":main.dll"],',
            '  outs = ["main_renamed.dll"],',
            '  cmd = "cp $< $@",',
            ')',
        ])
    bazel_bin = self.getBazelInfo('bazel-bin')

    exit_code, _, stderr = self.RunBazel([
        'build', '//main:main.dll',
        '--output_groups=default,runtime_dynamic_libraries,interface_library'
    ])
    self.AssertExitCode(exit_code, 0, stderr)

    main_library = os.path.join(bazel_bin, 'main/main.dll')
    main_interface = os.path.join(bazel_bin, 'main/main.dll.if.lib')
    def_file = os.path.join(bazel_bin, 'main/main.dll.gen.def')
    self.assertTrue(os.path.exists(main_library))
    self.assertTrue(os.path.exists(main_interface))
    self.assertTrue(os.path.exists(def_file))
    # A.dll and B.dll should be built and copied because they belong to
    # runtime_dynamic_libraries output group.
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'main/A_0.dll')))
    self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'main/B_0.dll')))
    # hello_A and hello_B should not be exported.
    self.AssertFileContentNotContains(def_file, 'hello_A')
    self.AssertFileContentNotContains(def_file, 'hello_B')
    self.AssertFileContentContains(def_file, 'hello_C')

    # The copy should succeed since //main:main.dll is only supposed to refer to
    # main.dll, A.dll and B.dll should be in a separate output group.
    exit_code, _, stderr = self.RunBazel(['build', '//main:renamed_main'])
    self.AssertExitCode(exit_code, 0, stderr)

  def testGetDefFileOfSharedLibraryFromCcBinary(self):
    self.createProjectFiles()
    self.ScratchFile(
        'main/BUILD',
        [
            'cc_binary(',
            '  name = "main.dll",',
            '  srcs = ["main.cc"],',
            '  deps = ["//:B"],',  # Transitively depends on //:A
            '  linkstatic = 1,'
            '  linkshared = 1,'
            ')',
        ])
    bazel_bin = self.getBazelInfo('bazel-bin')

    exit_code, _, stderr = self.RunBazel(
        ['build', '//main:main.dll', '--output_groups=def_file'])
    self.AssertExitCode(exit_code, 0, stderr)

    # Although windows_export_all_symbols is not specified for this target,
    # we should still be able to get the DEF file by def_file output group.
    def_file = os.path.join(bazel_bin, 'main/main.dll.gen.def')
    self.assertTrue(os.path.exists(def_file))
    self.AssertFileContentContains(def_file, 'hello_A')
    self.AssertFileContentContains(def_file, 'hello_B')
    self.AssertFileContentContains(def_file, 'hello_C')

  def testBuildSharedLibraryWithoutAnySymbolExported(self):
    self.createProjectFiles()
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "A.dll",',
        '  srcs = ["a.cc", "a.h"],',
        '  copts = ["/DNO_DLLEXPORT"],',
        '  linkshared = 1,'
        ')',
    ])
    bazel_bin = self.getBazelInfo('bazel-bin')

    exit_code, _, stderr = self.RunBazel(['build', '//:A.dll'])
    self.AssertExitCode(exit_code, 0, stderr)

    # Although windows_export_all_symbols is not specified for this target,
    # we should still be able to build a DLL without any symbol exported.
    empty_def_file = os.path.join(bazel_bin, 'A.dll.gen.empty.def')
    self.assertTrue(os.path.exists(empty_def_file))
    self.AssertFileContentNotContains(empty_def_file, 'hello_A')

  def testUsingDefFileGeneratedFromCcLibrary(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('lib_A.cc', ['void hello_A() {}'])
    self.ScratchFile('lib_B.cc', ['void hello_B() {}'])
    self.ScratchFile('BUILD', [
        'cc_library(',
        '  name = "lib_A",',
        '  srcs = ["lib_A.cc"],',
        ')',
        '',
        'cc_library(',
        '  name = "lib_B",',
        '  srcs = ["lib_B.cc"],',
        '  deps = [":lib_A"]',
        ')',
        '',
        'filegroup(',
        '  name = "lib_B_symbols",',
        '  srcs = [":lib_B"],',
        '  output_group = "def_file",',
        ')',
        '',
        'cc_binary(',
        '  name = "lib.dll",',
        '  deps = [":lib_B"],',
        '  win_def_file = ":lib_B_symbols",',
        '  linkshared = 1,',
        ')',
    ])
    # Test specifying DEF file in cc_binary
    bazel_bin = self.getBazelInfo('bazel-bin')
    exit_code, _, stderr = self.RunBazel(['build', '//:lib.dll', '-s'])
    self.AssertExitCode(exit_code, 0, stderr)
    def_file = bazel_bin + '/lib_B.gen.def'
    self.assertTrue(os.path.exists(def_file))
    # hello_A should not be exported
    self.AssertFileContentNotContains(def_file, 'hello_A')
    # hello_B should be exported
    self.AssertFileContentContains(def_file, 'hello_B')

  def testWinDefFileAttribute(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('lib.cc', ['void hello() {}'])
    self.ScratchFile('my_lib.def', [
        'EXPORTS',
        '        ?hello@@YAXXZ',
    ])
    self.ScratchFile('BUILD', [
        'cc_library(',
        '  name = "lib",',
        '  srcs = ["lib.cc"],',
        '  win_def_file = "my_lib.def",',
        ')',
        '',
        'cc_binary(',
        '  name = "lib_dy.dll",',
        '  srcs = ["lib.cc"],',
        '  win_def_file = "my_lib.def",',
        '  linkshared = 1,',
        ')',
    ])

    # Test exporting symbols using custom DEF file in cc_library.
    # Auto-generating DEF file should be disabled when custom DEF file specified
    # Rename DLL shoud be disabled when when custom DEF file specified
    exit_code, _, stderr = self.RunBazel([
        'build', '//:lib', '-s', '--output_groups=dynamic_library',
        '--features=windows_export_all_symbols'
    ])
    self.AssertExitCode(exit_code, 0, stderr)

    bazel_bin = self.getBazelInfo('bazel-bin')
    lib_if = os.path.join(bazel_bin, 'lib.if.lib')
    lib_def = os.path.join(bazel_bin, 'lib.gen.def')
    lib_dll = os.path.join(bazel_bin, 'lib.dll')
    self.assertTrue(os.path.exists(lib_if))
    self.assertFalse(os.path.exists(lib_def))
    self.assertTrue(os.path.exists(lib_dll))

    # Test specifying DEF file in cc_binary
    exit_code, _, stderr = self.RunBazel(['build', '//:lib_dy.dll', '-s'])
    self.AssertExitCode(exit_code, 0, stderr)
    filepath = bazel_bin + '/lib_dy.dll-2.params'
    with open(filepath, 'r', encoding='latin-1') as param_file:
      self.assertIn('/DEF:my_lib.def', param_file.read())

  def testCcImportRule(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('A.lib', [])
    self.ScratchFile('A.dll', [])
    self.ScratchFile('A.if.lib', [])
    self.ScratchFile('BUILD', [
        'cc_import(',
        '  name = "a_import",',
        '  static_library = "A.lib",',
        '  shared_library = "A.dll",',
        '  interface_library = "A.if.lib",',
        '  hdrs = ["a.h"],',
        '  alwayslink = 1,',
        ')',
    ])
    exit_code, _, stderr = self.RunBazel([
        'build', '//:a_import',
    ])
    self.AssertExitCode(exit_code, 0, stderr)

  def testCopyDLLAsSource(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_import(',
        '  name = "a_import",',
        '  shared_library = "A.dll",',
        '  visibility = ["//:__subpackages__"],',
        ')',
        ''
        'filegroup(',
        '  name = "bin_src",',
        '  srcs = ["bin.cc"],',
        '  visibility = ["//:__subpackages__"],',
        ')',
        '',
        'cc_binary(',
        '  name = "bin",',
        '  srcs = ["//:bin_src"],',
        '  deps = ["//:a_import"],',
        ')',
    ])
    self.ScratchFile('package/BUILD', [
        'cc_binary(',
        '  name = "dir1/dir2/bin",',
        '  srcs = ["//:bin_src"],',
        '  deps = ["//:a_import"],',
        ')',
    ])
    self.ScratchFile('A.dll')
    self.ScratchFile('bin.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])
    exit_code, _, stderr = self.RunBazel([
        'build',
        '//:bin',
        '//package:dir1/dir2/bin',
    ])
    self.AssertExitCode(exit_code, 0, stderr)

    bazel_bin = self.getBazelInfo('bazel-bin')
    # Even though A.dll is in the same package as bin.exe, it still should
    # be copied to the output directory of bin.exe.
    a_dll = os.path.join(bazel_bin, 'A.dll')
    self.assertTrue(os.path.exists(a_dll))

    nested_a_dll = os.path.join(bazel_bin, 'package/dir1/dir2/A.dll')
    self.assertTrue(os.path.exists(nested_a_dll))

  def testCppErrorShouldBeVisible(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "bad",',
        '  srcs = ["bad.cc"],',
        ')',
    ])
    self.ScratchFile('bad.cc', [
        'int main(int argc, char** argv) {',
        '  this_is_an_error();',
        '}',
    ])
    exit_code, stdout, stderr = self.RunBazel(['build', '//:bad'])
    self.AssertExitCode(exit_code, 1, stderr)
    self.assertIn('this_is_an_error', ''.join(stdout))

  def testBuildWithClangClByCompilerFlag(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--compiler=clang-cl',
        '--incompatible_enable_cc_toolchain_resolution=false', '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('clang-cl.exe', ''.join(stderr))

  def testBuildWithClangClByToolchainResolution(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE', [
        'register_execution_platforms(',
        '  ":windows_clang"',
        ')',
        '',
        'register_toolchains(',
        '  "@local_config_cc//:cc-toolchain-x64_windows-clang-cl",',
        ')',
    ])
    self.ScratchFile('BUILD', [
        'platform(',
        '  name = "windows_clang",',
        '  constraint_values = [',
        '    "@platforms//cpu:x86_64",',
        '    "@platforms//os:windows",',
        '    "@bazel_tools//tools/cpp:clang-cl",',
        '  ]',
        ')',
        '',
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--incompatible_enable_cc_toolchain_resolution=true',
        '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('clang-cl.exe', ''.join(stderr))

  def createSimpleCppWorkspace(self, name):
    work_dir = self.ScratchDir(name)
    self.ScratchFile(name + '/WORKSPACE', ['workspace(name = "%s")' % name])
    self.ScratchFile(
        name + '/BUILD',
        ['cc_library(name = "lib", srcs = ["lib.cc"], hdrs = ["lib.h"])'])
    self.ScratchFile(name + '/lib.h', ['void hello();'])
    self.ScratchFile(name + '/lib.cc', ['#include "lib.h"', 'void hello() {}'])
    return work_dir

  # Regression test for https://github.com/bazelbuild/bazel/issues/9172
  def testCacheBetweenWorkspaceWithDifferentNames(self):
    cache_dir = self.ScratchDir('cache')
    dir_a = self.createSimpleCppWorkspace('A')
    dir_b = self.createSimpleCppWorkspace('B')
    exit_code, _, stderr = self.RunBazel(
        ['build', '--disk_cache=' + cache_dir, ':lib'], cwd=dir_a)
    self.AssertExitCode(exit_code, 0, stderr)
    exit_code, _, stderr = self.RunBazel(
        ['build', '--disk_cache=' + cache_dir, ':lib'], cwd=dir_b)
    self.AssertExitCode(exit_code, 0, stderr)

  # Regression test for https://github.com/bazelbuild/bazel/issues/9321
  def testCcCompileWithTreeArtifactAsSource(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'load(":genccs.bzl", "genccs")',
        '',
        'genccs(',
        '    name = "gen_tree",',
        ')',
        '',
        'cc_library(',
        '    name = "main",',
        '    srcs = [ "gen_tree" ]',
        ')',
        '',
        'cc_binary(',
        '    name = "genccs",',
        '    srcs = [ "genccs.cpp" ],',
        ')',
    ])
    self.ScratchFile('genccs.bzl', [
        'def _impl(ctx):',
        '  tree = ctx.actions.declare_directory(ctx.attr.name + ".cc")',
        '  ctx.actions.run(',
        '    inputs = [],',
        '    outputs = [ tree ],',
        '    arguments = [ tree.path ],',
        '    progress_message = "Generating cc files into \'%s\'" % tree.path,',
        '    executable = ctx.executable._tool,',
        '  )',
        '',
        '  return [ DefaultInfo(files = depset([ tree ])) ]',
        '',
        'genccs = rule(',
        '  implementation = _impl,',
        '  attrs = {',
        '    "_tool": attr.label(',
        '      executable = True,',
        '      cfg = "exec",',
        '      allow_files = True,',
        '      default = Label("//:genccs"),',
        '    )',
        '  }',
        ')',
    ])
    self.ScratchFile('genccs.cpp', [
        '#include <fstream>',
        '#include <Windows.h>',
        'using namespace std;',
        '',
        'int main (int argc, char *argv[]) {',
        '  CreateDirectory(argv[1], NULL);',
        '  ofstream myfile;',
        '  myfile.open(string(argv[1]) + string("/foo.cpp"));',
        '  myfile << "int main() { return 42; }";',
        '  return 0;',
        '}',
    ])
    exit_code, _, stderr = self.RunBazel(['build', '//:main'])
    self.AssertExitCode(exit_code, 0, stderr)

  def testBuild32BitCppBinaryWithMsvcCL(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--cpu=x64_x86_windows',
        '--noincompatible_enable_cc_toolchain_resolution', '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('x86\\cl.exe', '\n'.join(stderr))

  def testBuildArmCppBinaryWithMsvcCL(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--cpu=x64_arm_windows',
        '--noincompatible_enable_cc_toolchain_resolution', '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('arm\\cl.exe', '\n'.join(stderr))

  def testBuildArm64CppBinaryWithMsvcCLAndCpuX64Arm64Windows(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--cpu=x64_arm64_windows',
        '--noincompatible_enable_cc_toolchain_resolution', '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('arm64\\cl.exe', '\n'.join(stderr))

  def testBuildCppBinaryWithMingwGCC(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])

    # Test build without debug and optimize modes.
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--compiler=mingw-gcc',
        '--noincompatible_enable_cc_toolchain_resolution', '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('mingw64\\bin\\gcc', '\n'.join(stderr))
    self.assertNotIn('-g -Og', ''.join(stderr))
    self.assertNotIn('-g0 -O3 -DNDEBUG -ffunction-sections -fdata-sections',
                     ''.join(stderr))
    self.assertNotIn('-Wl,--gc-sections', ''.join(stderr))

    # Test build in debug mode.
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--compiler=mingw-gcc',
        '--noincompatible_enable_cc_toolchain_resolution', '-c', 'dbg',
        '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('mingw64\\bin\\gcc', '\n'.join(stderr))
    self.assertIn('-g -Og', ''.join(stderr))
    self.assertNotIn('-g0 -O3 -DNDEBUG -ffunction-sections -fdata-sections',
                     ''.join(stderr))
    self.assertNotIn('-Wl,--gc-sections', ''.join(stderr))

    # Test build in optimize mode.
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--compiler=mingw-gcc',
        '--noincompatible_enable_cc_toolchain_resolution', '-c', 'opt',
        '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('mingw64\\bin\\gcc', '\n'.join(stderr))
    self.assertNotIn('-g -Og', ''.join(stderr))
    self.assertIn('-g0 -O3 -DNDEBUG -ffunction-sections -fdata-sections',
                  ''.join(stderr))
    self.assertIn('-Wl,--gc-sections', ''.join(stderr))

  def testBuildCppBinaryWithMsysGCC(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])

    bazel_output = self.getBazelInfo('output_path')
    paramfile = 'x64_windows-%s/bin/main.exe-2.params'

    # Test build without debug and optimize modes.
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--compiler=msys-gcc',
        '--noincompatible_enable_cc_toolchain_resolution', '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('usr\\bin\\gcc', '\n'.join(stderr))
    self.assertNotIn('-g -Og', ''.join(stderr))
    self.assertNotIn('-g0 -O3 -DNDEBUG -ffunction-sections -fdata-sections',
                     ''.join(stderr))
    self.AssertFileContentNotContains(
        os.path.join(bazel_output, paramfile % 'fastbuild'),
        '-Wl,--gc-sections')

    # Test build in debug mode.
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--compiler=msys-gcc',
        '--noincompatible_enable_cc_toolchain_resolution', '-c', 'dbg',
        '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('usr\\bin\\gcc', '\n'.join(stderr))
    self.assertIn('-g -Og', ''.join(stderr))
    self.assertNotIn('-g0 -O3 -DNDEBUG -ffunction-sections -fdata-sections',
                     ''.join(stderr))
    self.AssertFileContentNotContains(
        os.path.join(bazel_output, paramfile % 'dbg'), '-Wl,--gc-sections')

    # Test build in optimize mode.
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--compiler=msys-gcc',
        '--noincompatible_enable_cc_toolchain_resolution', '-c', 'opt',
        '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('usr\\bin\\gcc', '\n'.join(stderr))
    self.assertNotIn('-g -Og', ''.join(stderr))
    self.assertIn('-g0 -O3 -DNDEBUG -ffunction-sections -fdata-sections',
                  ''.join(stderr))
    self.AssertFileContentContains(
        os.path.join(bazel_output, paramfile % 'opt'), '-Wl,--gc-sections')

  def testBuildArm64CppBinaryWithMsvcCLAndCpuArm64Windows(self):
    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
    self.ScratchFile('BUILD', [
        'cc_binary(',
        '  name = "main",',
        '  srcs = ["main.cc"],',
        ')',
    ])
    self.ScratchFile('main.cc', [
        'int main() {',
        '  return 0;',
        '}',
    ])
    exit_code, _, stderr = self.RunBazel([
        'build', '-s', '--cpu=arm64_windows',
        '--noincompatible_enable_cc_toolchain_resolution', '//:main'
    ])
    self.AssertExitCode(exit_code, 0, stderr)
    self.assertIn('arm64\\cl.exe', ''.join(stderr))


if __name__ == '__main__':
  unittest.main()
